diff --git a/CMakeLists.txt b/CMakeLists.txt index 41fb0fbf9..7638d765c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,761 +1,769 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) project(KWin) set(PROJECT_VERSION "5.18.80") set(PROJECT_VERSION_MAJOR 5) set(QT_MIN_VERSION "5.12.0") set(KF5_MIN_VERSION "5.66.0") set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) find_package(ECM 5.38 REQUIRED NO_MODULE) include(FeatureSummary) include(WriteBasicConfigVersionFile) include(GenerateExportHeader) # where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is checked set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Concurrent Core DBus Quick QuickWidgets Script Sensors UiTools Widgets X11Extras ) find_package(Qt5Test ${QT_MIN_VERSION} CONFIG QUIET) set_package_properties(Qt5Test PROPERTIES PURPOSE "Required for tests" TYPE OPTIONAL ) add_feature_info("Qt5Test" Qt5Test_FOUND "Required for building tests") if (NOT Qt5Test_FOUND) set(BUILD_TESTING OFF CACHE BOOL "Build the testing tree.") endif() include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDEClangFormat) include(ECMInstallIcons) include(ECMOptionalAddSubdirectory) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0 -DQT_USE_QSTRINGBUILDER -DQT_NO_URL_CAST_FROM_STRING) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt5Multimedia QUIET) set_package_properties(Qt5Multimedia PROPERTIES PURPOSE "Runtime-only dependency for effect video playback" TYPE RUNTIME ) # required frameworks by Core find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Config ConfigWidgets CoreAddons Crash GlobalAccel I18n IconThemes IdleTime Notifications Package Plasma Wayland WidgetsAddons WindowSystem ) # required frameworks by config modules find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Completion Declarative KCMUtils KIO NewStuff Service TextWidgets XmlGui ) find_package(Threads) set_package_properties(Threads PROPERTIES PURPOSE "Needed for VirtualTerminal support in KWin Wayland" TYPE REQUIRED ) # optional frameworks find_package(KF5Activities ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5Activities PROPERTIES PURPOSE "Enable building of KWin with kactivities support" TYPE OPTIONAL ) add_feature_info("KF5Activities" KF5Activities_FOUND "Enable building of KWin with kactivities support") find_package(KF5DocTools ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5DocTools PROPERTIES PURPOSE "Enable building documentation" TYPE OPTIONAL ) add_feature_info("KF5DocTools" KF5DocTools_FOUND "Enable building documentation") find_package(KF5Kirigami2 ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5Kirigami2 PROPERTIES DESCRIPTION "A QtQuick based components set" PURPOSE "Required at runtime for Virtual desktop KCM and the virtual keyboard" TYPE RUNTIME ) find_package(KDecoration2 5.18.0 CONFIG REQUIRED) find_package(KScreenLocker CONFIG REQUIRED) set_package_properties(KScreenLocker PROPERTIES TYPE REQUIRED PURPOSE "For screenlocker integration in kwin_wayland" ) find_package(Breeze 5.9.0 CONFIG) set_package_properties(Breeze PROPERTIES TYPE OPTIONAL PURPOSE "For setting the default window decoration plugin" ) if (${Breeze_FOUND}) if (${BREEZE_WITH_KDECORATION}) set(HAVE_BREEZE_DECO true) else() set(HAVE_BREEZE_DECO FALSE) endif() else() set(HAVE_BREEZE_DECO FALSE) endif() add_feature_info("Breeze-Decoration" HAVE_BREEZE_DECO "Default decoration plugin Breeze") find_package(EGL) set_package_properties(EGL PROPERTIES TYPE RUNTIME PURPOSE "Required to build KWin with EGL support" ) find_package(epoxy) set_package_properties(epoxy PROPERTIES DESCRIPTION "libepoxy" URL "https://github.com/anholt/libepoxy" TYPE REQUIRED PURPOSE "OpenGL dispatch library" ) set(HAVE_DL_LIBRARY FALSE) if (epoxy_HAS_GLX) find_library(DL_LIBRARY dl) if (DL_LIBRARY) set(HAVE_DL_LIBRARY TRUE) endif() endif() find_package(Wayland 1.2 REQUIRED COMPONENTS Cursor OPTIONAL_COMPONENTS Egl) set_package_properties(Wayland PROPERTIES TYPE REQUIRED PURPOSE "Required for building KWin with Wayland support" ) add_feature_info("Wayland::EGL" Wayland_Egl_FOUND "Enable building of Wayland backend and QPA with EGL support.") set(HAVE_WAYLAND_EGL FALSE) if (Wayland_Egl_FOUND) set(HAVE_WAYLAND_EGL TRUE) endif() find_package(XKB 0.7.0) set_package_properties(XKB PROPERTIES TYPE REQUIRED PURPOSE "Required for building KWin with Wayland support" ) find_package(Libinput 1.9) set_package_properties(Libinput PROPERTIES TYPE REQUIRED PURPOSE "Required for input handling on Wayland.") find_package(UDev) set_package_properties(UDev PROPERTIES URL "https://www.freedesktop.org/wiki/Software/systemd/" DESCRIPTION "Linux device library." TYPE REQUIRED PURPOSE "Required for input handling on Wayland." ) find_package(Libdrm 2.4.62) set_package_properties(Libdrm PROPERTIES TYPE OPTIONAL PURPOSE "Required for drm output on Wayland.") set(HAVE_DRM FALSE) if (Libdrm_FOUND) set(HAVE_DRM TRUE) endif() find_package(gbm) set_package_properties(gbm PROPERTIES TYPE OPTIONAL PURPOSE "Required for egl output of drm backend.") set(HAVE_GBM FALSE) if (HAVE_DRM AND gbm_FOUND) set(HAVE_GBM TRUE) endif() option(KWIN_BUILD_EGL_STREAM_BACKEND "Enable building of EGLStream based DRM backend" ON) if (HAVE_DRM AND KWIN_BUILD_EGL_STREAM_BACKEND) set(HAVE_EGL_STREAMS TRUE) endif() find_package(libhybris) set_package_properties(libhybris PROPERTIES TYPE OPTIONAL PURPOSE "Required for libhybris backend") set(HAVE_LIBHYBRIS ${libhybris_FOUND}) find_package(X11) set_package_properties(X11 PROPERTIES DESCRIPTION "X11 libraries" URL "https://www.x.org" TYPE REQUIRED ) add_feature_info("XInput" X11_Xinput_FOUND "Required for poll-free mouse cursor updates") set(HAVE_X11_XINPUT ${X11_Xinput_FOUND}) # All the required XCB components find_package(XCB 1.10 REQUIRED COMPONENTS COMPOSITE CURSOR DAMAGE GLX ICCCM IMAGE KEYSYMS RANDR RENDER SHAPE SHM SYNC XCB XFIXES ) set_package_properties(XCB PROPERTIES TYPE REQUIRED) # and the optional XCB dependencies if (XCB_ICCCM_VERSION VERSION_LESS "0.4") set(XCB_ICCCM_FOUND FALSE) endif() add_feature_info("XCB-ICCCM" XCB_ICCCM_FOUND "Required for building test applications for KWin") find_package(X11_XCB) set_package_properties(X11_XCB PROPERTIES PURPOSE "Required for building X11 windowed backend of kwin_wayland" TYPE OPTIONAL ) # dependencies for QPA plugin find_package(Qt5FontDatabaseSupport REQUIRED) find_package(Qt5ThemeSupport REQUIRED) find_package(Qt5EventDispatcherSupport REQUIRED) find_package(Freetype REQUIRED) set_package_properties(Freetype PROPERTIES DESCRIPTION "A font rendering engine" URL "https://www.freetype.org" TYPE REQUIRED PURPOSE "Needed for KWin's QPA plugin." ) find_package(Fontconfig REQUIRED) set_package_properties(Fontconfig PROPERTIES TYPE REQUIRED PURPOSE "Needed for KWin's QPA plugin." ) find_package(Xwayland) set_package_properties(Xwayland PROPERTIES URL "https://x.org" DESCRIPTION "Xwayland X server" TYPE RUNTIME PURPOSE "Needed for running kwin_wayland" ) find_package(Libcap) set_package_properties(Libcap PROPERTIES TYPE OPTIONAL PURPOSE "Needed for running kwin_wayland with real-time scheduling policy" ) set(HAVE_LIBCAP ${Libcap_FOUND}) +find_package(hwdata) +set_package_properties(hwdata PROPERTIES + TYPE RUNTIME + PURPOSE "Runtime-only dependency needed for mapping monitor hardware vendor IDs to full names" + URL "https://github.com/vcrhonek/hwdata" +) +set(HAVE_HWDATA ${hwdata_FOUND}) + include(ECMQMLModules) ecm_find_qmlmodule(QtQuick 2.3) ecm_find_qmlmodule(QtQuick.Controls 1.2) ecm_find_qmlmodule(QtQuick.Layouts 1.3) ecm_find_qmlmodule(QtQuick.VirtualKeyboard 2.1) ecm_find_qmlmodule(QtQuick.Window 2.1) ecm_find_qmlmodule(QtMultimedia 5.0) ecm_find_qmlmodule(org.kde.kquickcontrolsaddons 2.0) ecm_find_qmlmodule(org.kde.plasma.core 2.0) ecm_find_qmlmodule(org.kde.plasma.components 2.0) ########### configure tests ############### include(CMakeDependentOption) option(KWIN_BUILD_DECORATIONS "Enable building of KWin decorations." ON) option(KWIN_BUILD_KCMS "Enable building of KWin configuration modules." ON) option(KWIN_BUILD_TABBOX "Enable building of KWin Tabbox functionality" ON) option(KWIN_BUILD_XRENDER_COMPOSITING "Enable building of KWin with XRender Compositing support" ON) cmake_dependent_option(KWIN_BUILD_ACTIVITIES "Enable building of KWin with kactivities support" ON "KF5Activities_FOUND" OFF) # Binary name of KWin set(KWIN_NAME "kwin") set(KWIN_INTERNAL_NAME_X11 "kwin_x11") set(KWIN_INTERNAL_NAME_WAYLAND "kwin_wayland") # KWIN_HAVE_XRENDER_COMPOSITING - whether XRender-based compositing support is available: may be disabled if (KWIN_BUILD_XRENDER_COMPOSITING) set(KWIN_HAVE_XRENDER_COMPOSITING 1) endif() include_directories(${XKB_INCLUDE_DIR}) include_directories(${epoxy_INCLUDE_DIR}) set(HAVE_EPOXY_GLX ${epoxy_HAS_GLX}) # for things that are also used by kwin libraries configure_file(libkwineffects/kwinconfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/libkwineffects/kwinconfig.h ) # for kwin internal things set(HAVE_X11_XCB ${X11_XCB_FOUND}) include(CheckIncludeFile) include(CheckIncludeFiles) include(CheckSymbolExists) check_include_files(unistd.h HAVE_UNISTD_H) check_include_files(malloc.h HAVE_MALLOC_H) check_include_file("sys/prctl.h" HAVE_SYS_PRCTL_H) check_symbol_exists(PR_SET_DUMPABLE "sys/prctl.h" HAVE_PR_SET_DUMPABLE) check_symbol_exists(PR_SET_PDEATHSIG "sys/prctl.h" HAVE_PR_SET_PDEATHSIG) check_include_file("sys/procctl.h" HAVE_SYS_PROCCTL_H) check_symbol_exists(PROC_TRACE_CTL "sys/procctl.h" HAVE_PROC_TRACE_CTL) if (HAVE_PR_SET_DUMPABLE OR HAVE_PROC_TRACE_CTL) set(CAN_DISABLE_PTRACE TRUE) endif() add_feature_info("prctl/procctl tracing control" CAN_DISABLE_PTRACE "Required for disallowing ptrace on kwin_wayland process") check_include_file("sys/sysmacros.h" HAVE_SYS_SYSMACROS_H) check_include_file("linux/vt.h" HAVE_LINUX_VT_H) add_feature_info("linux/vt.h" HAVE_LINUX_VT_H "Required for virtual terminal support under wayland") check_include_file("linux/fb.h" HAVE_LINUX_FB_H) add_feature_info("linux/fb.h" HAVE_LINUX_FB_H "Required for the fbdev backend") check_symbol_exists(SCHED_RESET_ON_FORK "sched.h" HAVE_SCHED_RESET_ON_FORK) add_feature_info("SCHED_RESET_ON_FORK" HAVE_SCHED_RESET_ON_FORK "Required for running kwin_wayland with real-time scheduling") configure_file(config-kwin.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kwin.h) ########### global ############### set(kwin_effects_dbus_xml ${CMAKE_CURRENT_SOURCE_DIR}/org.kde.kwin.Effects.xml) include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR}/libkwineffects ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/libkwineffects ${CMAKE_CURRENT_SOURCE_DIR}/effects ${CMAKE_CURRENT_SOURCE_DIR}/tabbox ${CMAKE_CURRENT_SOURCE_DIR}/platformsupport ) add_subdirectory(libkwineffects) if (KWIN_BUILD_KCMS) add_subdirectory(kcmkwin) endif() add_subdirectory(data) add_subdirectory(effects) add_subdirectory(scripts) add_subdirectory(tabbox) add_subdirectory(scripting) add_subdirectory(helpers) ########### next target ############### set(kwin_SRCS abstract_client.cpp abstract_opengl_context_attribute_builder.cpp abstract_output.cpp abstract_wayland_output.cpp activation.cpp appmenu.cpp atoms.cpp client_machine.cpp colorcorrection/clockskewnotifier.cpp colorcorrection/clockskewnotifierengine.cpp colorcorrection/colorcorrectdbusinterface.cpp colorcorrection/manager.cpp colorcorrection/suncalc.cpp composite.cpp cursor.cpp dbusinterface.cpp debug_console.cpp decorations/decoratedclient.cpp decorations/decorationbridge.cpp decorations/decorationpalette.cpp decorations/decorationrenderer.cpp decorations/decorations_logging.cpp decorations/settings.cpp deleted.cpp effectloader.cpp effects.cpp egl_context_attribute_builder.cpp events.cpp focuschain.cpp geometrytip.cpp gestures.cpp globalshortcuts.cpp group.cpp idle_inhibition.cpp input.cpp input_event.cpp input_event_spy.cpp internal_client.cpp keyboard_input.cpp keyboard_layout.cpp keyboard_layout_switching.cpp keyboard_repeat.cpp killwindow.cpp layers.cpp libinput/connection.cpp libinput/context.cpp libinput/device.cpp libinput/events.cpp libinput/libinput_logging.cpp linux_dmabuf.cpp logind.cpp main.cpp modifier_only_shortcuts.cpp moving_client_x11_filter.cpp netinfo.cpp onscreennotification.cpp options.cpp orientation_sensor.cpp osd.cpp outline.cpp outputscreens.cpp overlaywindow.cpp placement.cpp platform.cpp pointer_input.cpp popup_input_filter.cpp rootinfo_filter.cpp rules.cpp scene.cpp screenedge.cpp screenlockerwatcher.cpp screens.cpp scripting/dbuscall.cpp scripting/meta.cpp scripting/screenedgeitem.cpp scripting/scriptedeffect.cpp scripting/scripting.cpp scripting/scripting_logging.cpp scripting/scripting_model.cpp scripting/scriptingutils.cpp scripting/timer.cpp scripting/workspace_wrapper.cpp shadow.cpp sm.cpp thumbnailitem.cpp toplevel.cpp touch_hide_cursor_spy.cpp tablet_input.cpp touch_input.cpp udev.cpp unmanaged.cpp useractions.cpp utils.cpp virtualdesktops.cpp virtualdesktopsdbustypes.cpp virtualkeyboard.cpp virtualkeyboard_dbus.cpp was_user_interaction_x11_filter.cpp wayland_cursor_theme.cpp wayland_server.cpp window_property_notify_x11_filter.cpp workspace.cpp x11client.cpp x11eventfilter.cpp xcbutils.cpp xdgshellclient.cpp xkb.cpp xwl/xwayland_interface.cpp ) if (CMAKE_SYSTEM_NAME MATCHES "Linux") set(kwin_SRCS ${kwin_SRCS} colorcorrection/clockskewnotifierengine_linux.cpp ) endif() include(ECMQtDeclareLoggingCategory) ecm_qt_declare_logging_category(kwin_SRCS HEADER colorcorrect_logging.h IDENTIFIER KWIN_COLORCORRECTION CATEGORY_NAME kwin_colorcorrection DEFAULT_SEVERITY Critical ) if (KWIN_BUILD_TABBOX) include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS}) set(kwin_SRCS ${kwin_SRCS} tabbox/clientmodel.cpp tabbox/desktopchain.cpp tabbox/desktopmodel.cpp tabbox/switcheritem.cpp tabbox/tabbox.cpp tabbox/tabbox_logging.cpp tabbox/tabboxconfig.cpp tabbox/tabboxhandler.cpp tabbox/x11_filter.cpp ) endif() if (KWIN_BUILD_ACTIVITIES) set(kwin_SRCS ${kwin_SRCS} activities.cpp ) endif() if (HAVE_LINUX_VT_H) set(kwin_SRCS ${kwin_SRCS} virtual_terminal.cpp ) endif() kconfig_add_kcfg_files(kwin_SRCS settings.kcfgc) kconfig_add_kcfg_files(kwin_SRCS colorcorrection/colorcorrect_settings.kcfgc) qt5_add_dbus_adaptor(kwin_SRCS org.kde.KWin.xml dbusinterface.h KWin::DBusInterface) qt5_add_dbus_adaptor(kwin_SRCS org.kde.kwin.Compositing.xml dbusinterface.h KWin::CompositorDBusInterface) qt5_add_dbus_adaptor(kwin_SRCS org.kde.kwin.ColorCorrect.xml colorcorrection/colorcorrectdbusinterface.h KWin::ColorCorrect::ColorCorrectDBusInterface) qt5_add_dbus_adaptor(kwin_SRCS ${kwin_effects_dbus_xml} effects.h KWin::EffectsHandlerImpl) qt5_add_dbus_adaptor(kwin_SRCS org.kde.kwin.OrientationSensor.xml orientation_sensor.h KWin::OrientationSensor) qt5_add_dbus_adaptor(kwin_SRCS org.kde.KWin.VirtualDesktopManager.xml dbusinterface.h KWin::VirtualDesktopManagerDBusInterface) qt5_add_dbus_adaptor(kwin_SRCS org.kde.KWin.Session.xml sm.h KWin::SessionManager) qt5_add_dbus_interface(kwin_SRCS ${KSCREENLOCKER_DBUS_INTERFACES_DIR}/kf5_org.freedesktop.ScreenSaver.xml screenlocker_interface) qt5_add_dbus_interface(kwin_SRCS ${KSCREENLOCKER_DBUS_INTERFACES_DIR}/org.kde.screensaver.xml kscreenlocker_interface) qt5_add_dbus_interface(kwin_SRCS org.kde.kappmenu.xml appmenu_interface) ki18n_wrap_ui(kwin_SRCS debug_console.ui shortcutdialog.ui ) ########### target link libraries ############### set(kwin_OWN_LIBS kwineffects kwin4_effect_builtins ) set(kwin_QT_LIBS Qt5::Concurrent Qt5::DBus Qt5::Quick Qt5::Script Qt5::Sensors ) set(kwin_KDE_LIBS KF5::ConfigCore KF5::ConfigWidgets KF5::CoreAddons KF5::GlobalAccel KF5::GlobalAccelPrivate KF5::I18n KF5::Notifications KF5::Package KF5::Plasma KF5::QuickAddons KF5::WindowSystem KDecoration2::KDecoration KDecoration2::KDecoration2Private PW::KScreenLocker ) set(kwin_XLIB_LIBS ${X11_ICE_LIB} ${X11_SM_LIB} ${X11_X11_LIB} ) set(kwin_XCB_LIBS XCB::COMPOSITE XCB::DAMAGE XCB::GLX XCB::ICCCM XCB::KEYSYMS XCB::RANDR XCB::RENDER XCB::SHAPE XCB::SHM XCB::SYNC XCB::XCB XCB::XFIXES ) set(kwin_WAYLAND_LIBS KF5::WaylandClient KF5::WaylandServer Wayland::Cursor XKB::XKB ${CMAKE_THREAD_LIBS_INIT} ) if (KWIN_BUILD_ACTIVITIES) set(kwin_KDE_LIBS ${kwin_KDE_LIBS} KF5::Activities) endif() set(kwinLibs ${kwin_OWN_LIBS} ${kwin_QT_LIBS} ${kwin_KDE_LIBS} ${kwin_XLIB_LIBS} ${kwin_XCB_LIBS} ${kwin_WAYLAND_LIBS} ${UDEV_LIBS} Libinput::Libinput ) add_library(kwin SHARED ${kwin_SRCS}) set_target_properties(kwin PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} ) target_link_libraries(kwin ${kwinLibs}) generate_export_header(kwin EXPORT_FILE_NAME kwin_export.h) target_link_libraries(kwin kwinglutils ${epoxy_LIBRARY}) add_executable(kwin_x11 main_x11.cpp) target_link_libraries(kwin_x11 kwin KF5::Crash Qt5::X11Extras) install(TARGETS kwin ${INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP) install(TARGETS kwin_x11 ${INSTALL_TARGETS_DEFAULT_ARGS}) set(kwin_XWAYLAND_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/xwl/clipboard.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/databridge.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/dnd.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/drag.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/drag_wl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/drag_x.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/selection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/selection_source.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/transfer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/xwayland.cpp ) include(ECMQtDeclareLoggingCategory) ecm_qt_declare_logging_category(kwin_XWAYLAND_SRCS HEADER xwayland_logging.h IDENTIFIER KWIN_XWL CATEGORY_NAME kwin_xwl DEFAULT_SEVERITY Critical ) set(kwin_WAYLAND_SRCS main_wayland.cpp tabletmodemanager.cpp ) add_executable(kwin_wayland ${kwin_WAYLAND_SRCS} ${kwin_XWAYLAND_SRCS}) target_link_libraries(kwin_wayland kwin KF5::Crash) if (HAVE_LIBCAP) target_link_libraries(kwin_wayland ${Libcap_LIBRARIES}) endif() install(TARGETS kwin_wayland ${INSTALL_TARGETS_DEFAULT_ARGS}) if (HAVE_LIBCAP) install( CODE "execute_process( COMMAND ${SETCAP_EXECUTABLE} CAP_SYS_NICE=+ep \$ENV{DESTDIR}${CMAKE_INSTALL_FULL_BINDIR}/kwin_wayland)" ) endif() add_subdirectory(platformsupport) add_subdirectory(plugins) ########### install files ############### install(FILES kwin.kcfg DESTINATION ${KCFG_INSTALL_DIR} RENAME ${KWIN_NAME}.kcfg) install(FILES colorcorrection/colorcorrect_settings.kcfg DESTINATION ${KCFG_INSTALL_DIR} RENAME ${KWIN_NAME}_colorcorrect.kcfg) install(FILES kwin.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR} RENAME ${KWIN_NAME}.notifyrc) install( FILES org.kde.KWin.VirtualDesktopManager.xml org.kde.KWin.xml org.kde.kwin.ColorCorrect.xml org.kde.kwin.Compositing.xml org.kde.kwin.Effects.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kwin_export.h DESTINATION ${INCLUDE_INSTALL_DIR} COMPONENT Devel) # Install the KWin/Script service type install(FILES scripting/kwinscript.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR}) add_subdirectory(qml) if (BUILD_TESTING) add_subdirectory(autotests) add_subdirectory(tests) endif() if (KF5DocTools_FOUND) add_subdirectory(doc) endif() add_subdirectory(kconf_update) # 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}) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) include(CMakePackageConfigHelpers) set(CMAKECONFIG_INSTALL_DIR "${CMAKECONFIG_INSTALL_PREFIX}/KWinDBusInterface") configure_package_config_file(KWinDBusInterfaceConfig.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/KWinDBusInterfaceConfig.cmake" PATH_VARS KDE_INSTALL_DBUSINTERFACEDIR INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/KWinDBusInterfaceConfig.cmake DESTINATION ${CMAKECONFIG_INSTALL_DIR}) diff --git a/cmake/modules/Findhwdata.cmake b/cmake/modules/Findhwdata.cmake new file mode 100644 index 000000000..005edac43 --- /dev/null +++ b/cmake/modules/Findhwdata.cmake @@ -0,0 +1,47 @@ +# - Try to find hwdata +# Once done this will define +# +# hwdata_DIR - The hwdata directory +# hwdata_PNPIDS_FILE - File with mapping of hw vendor IDs to names +# hwdata_FOUND - The hwdata directory exists and contains pnp.ids file + +# Copyright (c) 2020 Daniel Vrátil +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +if (UNIX AND NOT APPLE) + find_path(hwdata_DIR NAMES hwdata/pnp.ids HINTS /usr/share ENV XDG_DATA_DIRS) + find_file(hwdata_PNPIDS_FILE NAMES hwdata/pnp.ids HINTS /usr/share) + if (hwdata_DIR-NOTFOUND OR hwdata_PNPIDS_FILE-NOTFOUND) + set(hwdata_FOUND FALSE) + else() + set(hwdata_FOUND TRUE) + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(hwdata DEFAULT_MSG hwdata_FOUND hwdata_DIR hwdata_PNPIDS_FILE) + + mark_as_advanced(hwdata_FOUND hwdata_DIR hwdata_PNPIDS_FILE) +endif() diff --git a/config-kwin.h.cmake b/config-kwin.h.cmake index 90efb3e58..4ab037e2a 100644 --- a/config-kwin.h.cmake +++ b/config-kwin.h.cmake @@ -1,43 +1,48 @@ #cmakedefine KWIN_BUILD_DECORATIONS 1 #cmakedefine KWIN_BUILD_TABBOX 1 #cmakedefine KWIN_BUILD_ACTIVITIES 1 #define KWIN_NAME "${KWIN_NAME}" #define KWIN_INTERNAL_NAME_X11 "${KWIN_INTERNAL_NAME_X11}" #define KWIN_CONFIG "${KWIN_NAME}rc" #define KWIN_VERSION_STRING "${PROJECT_VERSION}" #define XCB_VERSION_STRING "${XCB_VERSION}" #define KWIN_KILLER_BIN "${CMAKE_INSTALL_FULL_LIBEXECDIR}/kwin_killer_helper" #define KWIN_RULES_DIALOG_BIN "${CMAKE_INSTALL_FULL_LIBEXECDIR}/kwin_rules_dialog" #define KWIN_XCLIPBOARD_SYNC_BIN "${CMAKE_INSTALL_FULL_LIBEXECDIR}/org_kde_kwin_xclipboard_syncer" #cmakedefine01 HAVE_X11_XCB #cmakedefine01 HAVE_X11_XINPUT #cmakedefine01 HAVE_DRM #cmakedefine01 HAVE_GBM #cmakedefine01 HAVE_EGL_STREAMS #cmakedefine01 HAVE_LIBHYBRIS #cmakedefine01 HAVE_WAYLAND_EGL #cmakedefine01 HAVE_SYS_PRCTL_H #cmakedefine01 HAVE_PR_SET_DUMPABLE #cmakedefine01 HAVE_PR_SET_PDEATHSIG #cmakedefine01 HAVE_SYS_PROCCTL_H #cmakedefine01 HAVE_PROC_TRACE_CTL #cmakedefine01 HAVE_SYS_SYSMACROS_H #cmakedefine01 HAVE_BREEZE_DECO #cmakedefine01 HAVE_LIBCAP #cmakedefine01 HAVE_SCHED_RESET_ON_FORK +#cmakedefine01 HAVE_HWDATA #if HAVE_BREEZE_DECO #define BREEZE_KDECORATION_PLUGIN_ID "${BREEZE_KDECORATION_PLUGIN_ID}" #endif /* Define to 1 if you have the header file. */ #cmakedefine HAVE_UNISTD_H 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_MALLOC_H 1 #cmakedefine XCB_ICCCM_FOUND 1 #ifndef XCB_ICCCM_FOUND #define XCB_ICCCM_WM_STATE_WITHDRAWN 0 #define XCB_ICCCM_WM_STATE_NORMAL 1 #define XCB_ICCCM_WM_STATE_ICONIC 3 #endif + +#if HAVE_HWDATA +#cmakedefine HWDATA_PNPIDS_FILE "@hwdata_PNPIDS_FILE@" +#endif diff --git a/plugins/platforms/drm/drm_output.cpp b/plugins/platforms/drm/drm_output.cpp index a2760d1ea..15a423b8f 100644 --- a/plugins/platforms/drm/drm_output.cpp +++ b/plugins/platforms/drm/drm_output.cpp @@ -1,1106 +1,1107 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "drm_output.h" #include "drm_backend.h" #include "drm_object_plane.h" #include "drm_object_crtc.h" #include "drm_object_connector.h" #include "composite.h" #include "logind.h" #include "logging.h" #include "main.h" #include "orientation_sensor.h" #include "screens_drm.h" #include "wayland_server.h" // KWayland #include // KF5 #include #include #include // Qt #include #include #include // c++ #include // drm #include #include #include namespace KWin { DrmOutput::DrmOutput(DrmBackend *backend) : AbstractWaylandOutput(backend) , m_backend(backend) { } DrmOutput::~DrmOutput() { Q_ASSERT(!m_pageFlipPending); teardown(); } void DrmOutput::teardown() { if (m_deleted) { return; } m_deleted = true; hideCursor(); m_crtc->blank(); if (m_primaryPlane) { // TODO: when having multiple planes, also clean up these m_primaryPlane->setOutput(nullptr); if (m_backend->deleteBufferAfterPageFlip()) { delete m_primaryPlane->current(); } m_primaryPlane->setCurrent(nullptr); } m_crtc->setOutput(nullptr); m_conn->setOutput(nullptr); delete m_cursor[0]; delete m_cursor[1]; if (!m_pageFlipPending) { deleteLater(); } //else will be deleted in the page flip handler //this is needed so that the pageflipcallback handle isn't deleted } void DrmOutput::releaseGbm() { if (DrmBuffer *b = m_crtc->current()) { b->releaseGbm(); } if (m_primaryPlane && m_primaryPlane->current()) { m_primaryPlane->current()->releaseGbm(); } } bool DrmOutput::hideCursor() { return drmModeSetCursor(m_backend->fd(), m_crtc->id(), 0, 0, 0) == 0; } bool DrmOutput::showCursor(DrmDumbBuffer *c) { const QSize &s = c->size(); return drmModeSetCursor(m_backend->fd(), m_crtc->id(), c->handle(), s.width(), s.height()) == 0; } bool DrmOutput::showCursor() { const bool ret = showCursor(m_cursor[m_cursorIndex]); if (!ret) { return ret; } if (m_hasNewCursor) { m_cursorIndex = (m_cursorIndex + 1) % 2; m_hasNewCursor = false; } return ret; } // TODO: Do we need to handle the flipped cases differently? int transformToRotation(DrmOutput::Transform transform) { switch (transform) { case DrmOutput::Transform::Normal: case DrmOutput::Transform::Flipped: return 0; case DrmOutput::Transform::Rotated90: case DrmOutput::Transform::Flipped90: return 90; case DrmOutput::Transform::Rotated180: case DrmOutput::Transform::Flipped180: return 180; case DrmOutput::Transform::Rotated270: case DrmOutput::Transform::Flipped270: return 270; } Q_UNREACHABLE(); return 0; } QMatrix4x4 DrmOutput::matrixDisplay(const QSize &s) const { QMatrix4x4 matrix; const int angle = transformToRotation(transform()); if (angle) { const QSize center = s / 2; matrix.translate(center.width(), center.height()); matrix.rotate(angle, 0, 0, 1); matrix.translate(-center.width(), -center.height()); } matrix.scale(scale()); return matrix; } void DrmOutput::updateCursor() { QImage cursorImage = m_backend->softwareCursor(); if (cursorImage.isNull()) { return; } m_hasNewCursor = true; QImage *c = m_cursor[m_cursorIndex]->image(); c->fill(Qt::transparent); QPainter p; p.begin(c); p.setWorldTransform(matrixDisplay(QSize(cursorImage.width(), cursorImage.height())).toTransform()); p.drawImage(QPoint(0, 0), cursorImage); p.end(); } void DrmOutput::moveCursor(const QPoint &globalPos) { const QMatrix4x4 hotspotMatrix = matrixDisplay(m_backend->softwareCursor().size()); QPoint p = globalPos - AbstractWaylandOutput::globalPos(); // TODO: Do we need to handle the flipped cases differently? switch (transform()) { case Transform::Normal: case Transform::Flipped: break; case Transform::Rotated90: case Transform::Flipped90: p = QPoint(p.y(), pixelSize().height() - p.x()); break; case Transform::Rotated270: case Transform::Flipped270: p = QPoint(pixelSize().width() - p.y(), p.x()); break; case Transform::Rotated180: case Transform::Flipped180: p = QPoint(pixelSize().width() - p.x(), pixelSize().height() - p.y()); break; default: Q_UNREACHABLE(); } p *= scale(); p -= hotspotMatrix.map(m_backend->softwareCursorHotspot()); drmModeMoveCursor(m_backend->fd(), m_crtc->id(), p.x(), p.y()); } static QHash s_connectorNames = { {DRM_MODE_CONNECTOR_Unknown, QByteArrayLiteral("Unknown")}, {DRM_MODE_CONNECTOR_VGA, QByteArrayLiteral("VGA")}, {DRM_MODE_CONNECTOR_DVII, QByteArrayLiteral("DVI-I")}, {DRM_MODE_CONNECTOR_DVID, QByteArrayLiteral("DVI-D")}, {DRM_MODE_CONNECTOR_DVIA, QByteArrayLiteral("DVI-A")}, {DRM_MODE_CONNECTOR_Composite, QByteArrayLiteral("Composite")}, {DRM_MODE_CONNECTOR_SVIDEO, QByteArrayLiteral("SVIDEO")}, {DRM_MODE_CONNECTOR_LVDS, QByteArrayLiteral("LVDS")}, {DRM_MODE_CONNECTOR_Component, QByteArrayLiteral("Component")}, {DRM_MODE_CONNECTOR_9PinDIN, QByteArrayLiteral("DIN")}, {DRM_MODE_CONNECTOR_DisplayPort, QByteArrayLiteral("DP")}, {DRM_MODE_CONNECTOR_HDMIA, QByteArrayLiteral("HDMI-A")}, {DRM_MODE_CONNECTOR_HDMIB, QByteArrayLiteral("HDMI-B")}, {DRM_MODE_CONNECTOR_TV, QByteArrayLiteral("TV")}, {DRM_MODE_CONNECTOR_eDP, QByteArrayLiteral("eDP")}, {DRM_MODE_CONNECTOR_VIRTUAL, QByteArrayLiteral("Virtual")}, {DRM_MODE_CONNECTOR_DSI, QByteArrayLiteral("DSI")}, #ifdef DRM_MODE_CONNECTOR_DPI {DRM_MODE_CONNECTOR_DPI, QByteArrayLiteral("DPI")}, #endif }; namespace { quint64 refreshRateForMode(_drmModeModeInfo *m) { // Calculate higher precision (mHz) refresh rate // logic based on Weston, see compositor-drm.c quint64 refreshRate = (m->clock * 1000000LL / m->htotal + m->vtotal / 2) / m->vtotal; if (m->flags & DRM_MODE_FLAG_INTERLACE) { refreshRate *= 2; } if (m->flags & DRM_MODE_FLAG_DBLSCAN) { refreshRate /= 2; } if (m->vscan > 1) { refreshRate /= m->vscan; } return refreshRate; } } bool DrmOutput::init(drmModeConnector *connector) { initEdid(connector); initDpms(connector); initUuid(); if (m_backend->atomicModeSetting()) { if (!initPrimaryPlane()) { return false; } } setInternal(connector->connector_type == DRM_MODE_CONNECTOR_LVDS || connector->connector_type == DRM_MODE_CONNECTOR_eDP || connector->connector_type == DRM_MODE_CONNECTOR_DSI); setDpmsSupported(true); if (isInternal()) { connect(kwinApp(), &Application::screensCreated, this, [this] { connect(screens()->orientationSensor(), &OrientationSensor::orientationChanged, this, &DrmOutput::automaticRotation); } ); } initOutputDevice(connector); if (!m_backend->atomicModeSetting() && !m_crtc->blank()) { // We use legacy mode and the initial output blank failed. return false; } updateDpms(KWayland::Server::OutputInterface::DpmsMode::On); return true; } void DrmOutput::initUuid() { QCryptographicHash hash(QCryptographicHash::Md5); hash.addData(QByteArray::number(m_conn->id())); hash.addData(m_edid.eisaId()); hash.addData(m_edid.monitorName()); hash.addData(m_edid.serialNumber()); m_uuid = hash.result().toHex().left(10); } void DrmOutput::initOutputDevice(drmModeConnector *connector) { QString manufacturer; - if (!m_edid.eisaId().isEmpty()) { + if (!m_edid.vendor().isEmpty()) { + manufacturer = QString::fromLatin1(m_edid.vendor()); + } else if (!m_edid.eisaId().isEmpty()) { manufacturer = QString::fromLatin1(m_edid.eisaId()); } QString connectorName = s_connectorNames.value(connector->connector_type, QByteArrayLiteral("Unknown")); QString modelName; if (!m_edid.monitorName().isEmpty()) { QString m = QString::fromLatin1(m_edid.monitorName()); if (!m_edid.serialNumber().isEmpty()) { m.append('/'); m.append(QString::fromLatin1(m_edid.serialNumber())); } modelName = m; } else if (!m_edid.serialNumber().isEmpty()) { modelName = QString::fromLatin1(m_edid.serialNumber()); } else { modelName = i18n("unknown"); } const QString model = connectorName + QStringLiteral("-") + QString::number(connector->connector_type_id) + QStringLiteral("-") + modelName; // read in mode information QVector modes; for (int i = 0; i < connector->count_modes; ++i) { // TODO: in AMS here we could read and store for later every mode's blob_id // would simplify isCurrentMode(..) and presentAtomically(..) in case of mode set auto *m = &connector->modes[i]; KWayland::Server::OutputDeviceInterface::ModeFlags deviceflags; if (isCurrentMode(m)) { deviceflags |= KWayland::Server::OutputDeviceInterface::ModeFlag::Current; } if (m->type & DRM_MODE_TYPE_PREFERRED) { deviceflags |= KWayland::Server::OutputDeviceInterface::ModeFlag::Preferred; } KWayland::Server::OutputDeviceInterface::Mode mode; mode.id = i; mode.size = QSize(m->hdisplay, m->vdisplay); mode.flags = deviceflags; mode.refreshRate = refreshRateForMode(m); modes << mode; } QSize physicalSize = !m_edid.physicalSize().isEmpty() ? m_edid.physicalSize() : QSize(connector->mmWidth, connector->mmHeight); // the size might be completely borked. E.g. Samsung SyncMaster 2494HS reports 160x90 while in truth it's 520x292 // as this information is used to calculate DPI info, it's going to result in everything being huge const QByteArray unknown = QByteArrayLiteral("unknown"); KConfigGroup group = kwinApp()->config()->group("EdidOverwrite").group(m_edid.eisaId().isEmpty() ? unknown : m_edid.eisaId()) .group(m_edid.monitorName().isEmpty() ? unknown : m_edid.monitorName()) .group(m_edid.serialNumber().isEmpty() ? unknown : m_edid.serialNumber()); if (group.hasKey("PhysicalSize")) { const QSize overwriteSize = group.readEntry("PhysicalSize", physicalSize); qCWarning(KWIN_DRM) << "Overwriting monitor physical size for" << m_edid.eisaId() << "/" << m_edid.monitorName() << "/" << m_edid.serialNumber() << " from " << physicalSize << "to " << overwriteSize; physicalSize = overwriteSize; } initInterfaces(model, manufacturer, m_uuid, physicalSize, modes); } bool DrmOutput::isCurrentMode(const drmModeModeInfo *mode) const { return mode->clock == m_mode.clock && mode->hdisplay == m_mode.hdisplay && mode->hsync_start == m_mode.hsync_start && mode->hsync_end == m_mode.hsync_end && mode->htotal == m_mode.htotal && mode->hskew == m_mode.hskew && mode->vdisplay == m_mode.vdisplay && mode->vsync_start == m_mode.vsync_start && mode->vsync_end == m_mode.vsync_end && mode->vtotal == m_mode.vtotal && mode->vscan == m_mode.vscan && mode->vrefresh == m_mode.vrefresh && mode->flags == m_mode.flags && mode->type == m_mode.type && qstrcmp(mode->name, m_mode.name) == 0; } - void DrmOutput::initEdid(drmModeConnector *connector) { DrmScopedPointer edid; for (int i = 0; i < connector->count_props; ++i) { DrmScopedPointer property(drmModeGetProperty(m_backend->fd(), connector->props[i])); if (!property) { continue; } if ((property->flags & DRM_MODE_PROP_BLOB) && qstrcmp(property->name, "EDID") == 0) { edid.reset(drmModeGetPropertyBlob(m_backend->fd(), connector->prop_values[i])); } } if (!edid) { return; } m_edid = Edid(edid->data, edid->length); if (!m_edid.isValid()) { qCWarning(KWIN_DRM, "Couldn't parse EDID for connector with id %d", m_conn->id()); } } bool DrmOutput::initPrimaryPlane() { for (int i = 0; i < m_backend->planes().size(); ++i) { DrmPlane* p = m_backend->planes()[i]; if (!p) { continue; } if (p->type() != DrmPlane::TypeIndex::Primary) { continue; } if (p->output()) { // Plane already has an output continue; } if (m_primaryPlane) { // Output already has a primary plane continue; } if (!p->isCrtcSupported(m_crtc->resIndex())) { continue; } p->setOutput(this); m_primaryPlane = p; qCDebug(KWIN_DRM) << "Initialized primary plane" << p->id() << "on CRTC" << m_crtc->id(); return true; } qCCritical(KWIN_DRM) << "Failed to initialize primary plane."; return false; } bool DrmOutput::initCursorPlane() // TODO: Add call in init (but needs layer support in general first) { for (int i = 0; i < m_backend->planes().size(); ++i) { DrmPlane* p = m_backend->planes()[i]; if (!p) { continue; } if (p->type() != DrmPlane::TypeIndex::Cursor) { continue; } if (p->output()) { // Plane already has an output continue; } if (m_cursorPlane) { // Output already has a cursor plane continue; } if (!p->isCrtcSupported(m_crtc->resIndex())) { continue; } p->setOutput(this); m_cursorPlane = p; qCDebug(KWIN_DRM) << "Initialized cursor plane" << p->id() << "on CRTC" << m_crtc->id(); return true; } return false; } bool DrmOutput::initCursor(const QSize &cursorSize) { auto createCursor = [this, cursorSize] (int index) { m_cursor[index] = m_backend->createBuffer(cursorSize); if (!m_cursor[index]->map(QImage::Format_ARGB32_Premultiplied)) { return false; } return true; }; if (!createCursor(0) || !createCursor(1)) { return false; } return true; } void DrmOutput::initDpms(drmModeConnector *connector) { for (int i = 0; i < connector->count_props; ++i) { DrmScopedPointer property(drmModeGetProperty(m_backend->fd(), connector->props[i])); if (!property) { continue; } if (qstrcmp(property->name, "DPMS") == 0) { m_dpms.swap(property); break; } } } void DrmOutput::updateEnablement(bool enable) { if (enable) { m_dpmsModePending = DpmsMode::On; if (m_backend->atomicModeSetting()) { atomicEnable(); } else { if (dpmsLegacyApply()) { m_backend->enableOutput(this, true); } } } else { m_dpmsModePending = DpmsMode::Off; if (m_backend->atomicModeSetting()) { atomicDisable(); } else { if (dpmsLegacyApply()) { m_backend->enableOutput(this, false); } } } } void DrmOutput::atomicEnable() { m_modesetRequested = true; if (m_atomicOffPending) { Q_ASSERT(m_pageFlipPending); m_atomicOffPending = false; } m_backend->enableOutput(this, true); if (Compositor *compositor = Compositor::self()) { compositor->addRepaintFull(); } } void DrmOutput::atomicDisable() { m_modesetRequested = true; m_backend->enableOutput(this, false); m_atomicOffPending = true; if (!m_pageFlipPending) { dpmsAtomicOff(); } } static DrmOutput::DpmsMode fromWaylandDpmsMode(KWayland::Server::OutputInterface::DpmsMode wlMode) { using namespace KWayland::Server; switch (wlMode) { case OutputInterface::DpmsMode::On: return DrmOutput::DpmsMode::On; case OutputInterface::DpmsMode::Standby: return DrmOutput::DpmsMode::Standby; case OutputInterface::DpmsMode::Suspend: return DrmOutput::DpmsMode::Suspend; case OutputInterface::DpmsMode::Off: return DrmOutput::DpmsMode::Off; default: Q_UNREACHABLE(); } } static KWayland::Server::OutputInterface::DpmsMode toWaylandDpmsMode(DrmOutput::DpmsMode mode) { using namespace KWayland::Server; switch (mode) { case DrmOutput::DpmsMode::On: return OutputInterface::DpmsMode::On; case DrmOutput::DpmsMode::Standby: return OutputInterface::DpmsMode::Standby; case DrmOutput::DpmsMode::Suspend: return OutputInterface::DpmsMode::Suspend; case DrmOutput::DpmsMode::Off: return OutputInterface::DpmsMode::Off; default: Q_UNREACHABLE(); } } void DrmOutput::updateDpms(KWayland::Server::OutputInterface::DpmsMode mode) { if (m_dpms.isNull() || !isEnabled()) { return; } const auto drmMode = fromWaylandDpmsMode(mode); if (drmMode == m_dpmsModePending) { qCDebug(KWIN_DRM) << "New DPMS mode equals old mode. DPMS unchanged."; return; } m_dpmsModePending = drmMode; if (m_backend->atomicModeSetting()) { m_modesetRequested = true; if (drmMode == DpmsMode::On) { if (m_atomicOffPending) { Q_ASSERT(m_pageFlipPending); m_atomicOffPending = false; } dpmsFinishOn(); } else { m_atomicOffPending = true; if (!m_pageFlipPending) { dpmsAtomicOff(); } } } else { dpmsLegacyApply(); } } void DrmOutput::dpmsFinishOn() { qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to On."; auto wlOutput = waylandOutput(); if (wlOutput) { wlOutput->setDpmsMode(toWaylandDpmsMode(DpmsMode::On)); } m_backend->checkOutputsAreOn(); if (!m_backend->atomicModeSetting()) { m_crtc->blank(); } if (Compositor *compositor = Compositor::self()) { compositor->addRepaintFull(); } } void DrmOutput::dpmsFinishOff() { qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to Off."; if (isEnabled()) { waylandOutput()->setDpmsMode(toWaylandDpmsMode(m_dpmsModePending)); m_backend->createDpmsFilter(); } } bool DrmOutput::dpmsLegacyApply() { if (drmModeConnectorSetProperty(m_backend->fd(), m_conn->id(), m_dpms->prop_id, uint64_t(m_dpmsModePending)) < 0) { m_dpmsModePending = m_dpmsMode; qCWarning(KWIN_DRM) << "Setting DPMS failed"; return false; } if (m_dpmsModePending == DpmsMode::On) { dpmsFinishOn(); } else { dpmsFinishOff(); } m_dpmsMode = m_dpmsModePending; return true; } void DrmOutput::updateTransform(Transform transform) { DrmPlane::Transformation planeTransform; // TODO: Do we want to support reflections (flips)? switch (transform) { case Transform::Normal: case Transform::Flipped: planeTransform = DrmPlane::Transformation::Rotate0; break; case Transform::Rotated90: case Transform::Flipped90: planeTransform = DrmPlane::Transformation::Rotate90; break; case Transform::Rotated180: case Transform::Flipped180: planeTransform = DrmPlane::Transformation::Rotate180; break; case Transform::Rotated270: case Transform::Flipped270: planeTransform = DrmPlane::Transformation::Rotate270; break; default: Q_UNREACHABLE(); } if (m_primaryPlane) { m_primaryPlane->setTransformation(planeTransform); } m_modesetRequested = true; // the cursor might need to get rotated updateCursor(); showCursor(); } void DrmOutput::updateMode(int modeIndex) { // get all modes on the connector DrmScopedPointer connector(drmModeGetConnector(m_backend->fd(), m_conn->id())); if (connector->count_modes <= modeIndex) { // TODO: error? return; } if (isCurrentMode(&connector->modes[modeIndex])) { // nothing to do return; } m_mode = connector->modes[modeIndex]; m_modesetRequested = true; setWaylandMode(); } void DrmOutput::setWaylandMode() { AbstractWaylandOutput::setWaylandMode(QSize(m_mode.hdisplay, m_mode.vdisplay), refreshRateForMode(&m_mode)); } void DrmOutput::pageFlipped() { // In legacy mode we might get a page flip through a blank. Q_ASSERT(m_pageFlipPending || !m_backend->atomicModeSetting()); m_pageFlipPending = false; if (m_deleted) { deleteLater(); return; } if (!m_crtc) { return; } // Egl based surface buffers get destroyed, QPainter based dumb buffers not // TODO: split up DrmOutput in two for dumb and egl/gbm surface buffer compatible subclasses completely? if (m_backend->deleteBufferAfterPageFlip()) { if (m_backend->atomicModeSetting()) { if (!m_primaryPlane->next()) { // on manual vt switch // TODO: when we later use overlay planes it might happen, that we have a page flip with only // damage on one of these, and therefore the primary plane has no next buffer // -> Then we don't want to return here! if (m_primaryPlane->current()) { m_primaryPlane->current()->releaseGbm(); } return; } for (DrmPlane *p : m_nextPlanesFlipList) { p->flipBufferWithDelete(); } m_nextPlanesFlipList.clear(); } else { if (!m_crtc->next()) { // on manual vt switch if (DrmBuffer *b = m_crtc->current()) { b->releaseGbm(); } } m_crtc->flipBuffer(); } } else { if (m_backend->atomicModeSetting()){ for (DrmPlane *p : m_nextPlanesFlipList) { p->flipBuffer(); } m_nextPlanesFlipList.clear(); } else { m_crtc->flipBuffer(); } m_crtc->flipBuffer(); } if (m_atomicOffPending) { dpmsAtomicOff(); } } bool DrmOutput::present(DrmBuffer *buffer) { if (m_dpmsModePending != DpmsMode::On) { return false; } if (m_backend->atomicModeSetting()) { return presentAtomically(buffer); } else { return presentLegacy(buffer); } } bool DrmOutput::dpmsAtomicOff() { m_atomicOffPending = false; // TODO: With multiple planes: deactivate all of them here delete m_primaryPlane->next(); m_primaryPlane->setNext(nullptr); m_nextPlanesFlipList << m_primaryPlane; if (!doAtomicCommit(AtomicCommitMode::Test)) { qCDebug(KWIN_DRM) << "Atomic test commit to Dpms Off failed. Aborting."; return false; } if (!doAtomicCommit(AtomicCommitMode::Real)) { qCDebug(KWIN_DRM) << "Atomic commit to Dpms Off failed. This should have never happened! Aborting."; return false; } m_nextPlanesFlipList.clear(); dpmsFinishOff(); return true; } bool DrmOutput::presentAtomically(DrmBuffer *buffer) { if (!LogindIntegration::self()->isActiveSession()) { qCWarning(KWIN_DRM) << "Logind session not active."; return false; } if (m_pageFlipPending) { qCWarning(KWIN_DRM) << "Page not yet flipped."; return false; } #if HAVE_EGL_STREAMS if (m_backend->useEglStreams() && !m_modesetRequested) { // EglStreamBackend queues normal page flips through EGL, // modesets are still performed through DRM-KMS m_pageFlipPending = true; return true; } #endif m_primaryPlane->setNext(buffer); m_nextPlanesFlipList << m_primaryPlane; if (!doAtomicCommit(AtomicCommitMode::Test)) { //TODO: When we use planes for layered rendering, fallback to renderer instead. Also for direct scanout? //TODO: Probably should undo setNext and reset the flip list qCDebug(KWIN_DRM) << "Atomic test commit failed. Aborting present."; // go back to previous state if (m_lastWorkingState.valid) { m_mode = m_lastWorkingState.mode; setTransform(m_lastWorkingState.transform); setGlobalPos(m_lastWorkingState.globalPos); if (m_primaryPlane) { m_primaryPlane->setTransformation(m_lastWorkingState.planeTransformations); } m_modesetRequested = true; // the cursor might need to get rotated updateCursor(); showCursor(); // TODO: forward to OutputInterface and OutputDeviceInterface setWaylandMode(); emit screens()->changed(); } return false; } const bool wasModeset = m_modesetRequested; if (!doAtomicCommit(AtomicCommitMode::Real)) { qCDebug(KWIN_DRM) << "Atomic commit failed. This should have never happened! Aborting present."; //TODO: Probably should undo setNext and reset the flip list return false; } if (wasModeset) { // store current mode set as new good state m_lastWorkingState.mode = m_mode; m_lastWorkingState.transform = transform(); m_lastWorkingState.globalPos = globalPos(); if (m_primaryPlane) { m_lastWorkingState.planeTransformations = m_primaryPlane->transformation(); } m_lastWorkingState.valid = true; } m_pageFlipPending = true; return true; } bool DrmOutput::presentLegacy(DrmBuffer *buffer) { if (m_crtc->next()) { return false; } if (!LogindIntegration::self()->isActiveSession()) { m_crtc->setNext(buffer); return false; } // Do we need to set a new mode first? if (!m_crtc->current() || m_crtc->current()->needsModeChange(buffer)) { if (!setModeLegacy(buffer)) { return false; } } const bool ok = drmModePageFlip(m_backend->fd(), m_crtc->id(), buffer->bufferId(), DRM_MODE_PAGE_FLIP_EVENT, this) == 0; if (ok) { m_crtc->setNext(buffer); } else { qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno); } return ok; } bool DrmOutput::setModeLegacy(DrmBuffer *buffer) { uint32_t connId = m_conn->id(); if (drmModeSetCrtc(m_backend->fd(), m_crtc->id(), buffer->bufferId(), 0, 0, &connId, 1, &m_mode) == 0) { return true; } else { qCWarning(KWIN_DRM) << "Mode setting failed"; return false; } } bool DrmOutput::doAtomicCommit(AtomicCommitMode mode) { drmModeAtomicReq *req = drmModeAtomicAlloc(); auto errorHandler = [this, mode, req] () { if (mode == AtomicCommitMode::Test) { // TODO: when we later test overlay planes, make sure we change only the right stuff back } if (req) { drmModeAtomicFree(req); } if (m_dpmsMode != m_dpmsModePending) { qCWarning(KWIN_DRM) << "Setting DPMS failed"; m_dpmsModePending = m_dpmsMode; if (m_dpmsMode != DpmsMode::On) { dpmsFinishOff(); } } // TODO: see above, rework later for overlay planes! for (DrmPlane *p : m_nextPlanesFlipList) { p->setNext(nullptr); } m_nextPlanesFlipList.clear(); }; if (!req) { qCWarning(KWIN_DRM) << "DRM: couldn't allocate atomic request"; errorHandler(); return false; } uint32_t flags = 0; // Do we need to set a new mode? if (m_modesetRequested) { if (m_dpmsModePending == DpmsMode::On) { if (drmModeCreatePropertyBlob(m_backend->fd(), &m_mode, sizeof(m_mode), &m_blobId) != 0) { qCWarning(KWIN_DRM) << "Failed to create property blob"; errorHandler(); return false; } } if (!atomicReqModesetPopulate(req, m_dpmsModePending == DpmsMode::On)){ qCWarning(KWIN_DRM) << "Failed to populate Atomic Modeset"; errorHandler(); return false; } flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; } if (mode == AtomicCommitMode::Real) { if (m_dpmsModePending == DpmsMode::On) { if (!(flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) { // TODO: Evaluating this condition should only be necessary, as long as we expect older kernels than 4.10. flags |= DRM_MODE_ATOMIC_NONBLOCK; } #if HAVE_EGL_STREAMS if (!m_backend->useEglStreams()) // EglStreamBackend uses the NV_output_drm_flip_event EGL extension // to register the flip event through eglStreamConsumerAcquireAttribNV #endif flags |= DRM_MODE_PAGE_FLIP_EVENT; } } else { flags |= DRM_MODE_ATOMIC_TEST_ONLY; } bool ret = true; // TODO: Make sure when we use more than one plane at a time, that we go through this list in the right order. for (int i = m_nextPlanesFlipList.size() - 1; 0 <= i; i-- ) { DrmPlane *p = m_nextPlanesFlipList[i]; ret &= p->atomicPopulate(req); } if (!ret) { qCWarning(KWIN_DRM) << "Failed to populate atomic planes. Abort atomic commit!"; errorHandler(); return false; } if (drmModeAtomicCommit(m_backend->fd(), req, flags, this)) { qCWarning(KWIN_DRM) << "Atomic request failed to commit:" << strerror(errno); errorHandler(); return false; } if (mode == AtomicCommitMode::Real && (flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) { qCDebug(KWIN_DRM) << "Atomic Modeset successful."; m_modesetRequested = false; m_dpmsMode = m_dpmsModePending; } drmModeAtomicFree(req); return true; } bool DrmOutput::atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable) { if (enable) { m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcX), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcY), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcW), m_mode.hdisplay << 16); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcH), m_mode.vdisplay << 16); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcW), m_mode.hdisplay); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcH), m_mode.vdisplay); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcId), m_crtc->id()); } else { if (m_backend->deleteBufferAfterPageFlip()) { delete m_primaryPlane->current(); delete m_primaryPlane->next(); } m_primaryPlane->setCurrent(nullptr); m_primaryPlane->setNext(nullptr); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcX), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcY), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcW), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcH), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcW), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcH), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcId), 0); } m_conn->setValue(int(DrmConnector::PropertyIndex::CrtcId), enable ? m_crtc->id() : 0); m_crtc->setValue(int(DrmCrtc::PropertyIndex::ModeId), enable ? m_blobId : 0); m_crtc->setValue(int(DrmCrtc::PropertyIndex::Active), enable); bool ret = true; ret &= m_conn->atomicPopulate(req); ret &= m_crtc->atomicPopulate(req); return ret; } bool DrmOutput::supportsTransformations() const { if (!m_primaryPlane) { return false; } const auto transformations = m_primaryPlane->supportedTransformations(); return transformations.testFlag(DrmPlane::Transformation::Rotate90) || transformations.testFlag(DrmPlane::Transformation::Rotate180) || transformations.testFlag(DrmPlane::Transformation::Rotate270); } void DrmOutput::automaticRotation() { if (!m_primaryPlane) { return; } const auto supportedTransformations = m_primaryPlane->supportedTransformations(); const auto requestedTransformation = screens()->orientationSensor()->orientation(); Transform newTransformation = Transform::Normal; switch (requestedTransformation) { case OrientationSensor::Orientation::TopUp: newTransformation = Transform::Normal; break; case OrientationSensor::Orientation::TopDown: if (!supportedTransformations.testFlag(DrmPlane::Transformation::Rotate180)) { return; } newTransformation = Transform::Rotated180; break; case OrientationSensor::Orientation::LeftUp: if (!supportedTransformations.testFlag(DrmPlane::Transformation::Rotate90)) { return; } newTransformation = Transform::Rotated90; break; case OrientationSensor::Orientation::RightUp: if (!supportedTransformations.testFlag(DrmPlane::Transformation::Rotate270)) { return; } newTransformation = Transform::Rotated270; break; case OrientationSensor::Orientation::FaceUp: case OrientationSensor::Orientation::FaceDown: case OrientationSensor::Orientation::Undefined: // unsupported return; } setTransform(newTransformation); emit screens()->changed(); } int DrmOutput::gammaRampSize() const { return m_crtc->gammaRampSize(); } bool DrmOutput::setGammaRamp(const GammaRamp &gamma) { return m_crtc->setGammaRamp(gamma); } } diff --git a/plugins/platforms/drm/edid.cpp b/plugins/platforms/drm/edid.cpp index 839b8363d..cfed32503 100644 --- a/plugins/platforms/drm/edid.cpp +++ b/plugins/platforms/drm/edid.cpp @@ -1,183 +1,217 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Flöser Copyright (C) 2019 Vlad Zahorodnii This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "edid.h" +#include "config-kwin.h" + +#include namespace KWin { static bool verifyHeader(const uint8_t *data) { if (data[0] != 0x0 || data[7] != 0x0) { return false; } return std::all_of(data + 1, data + 7, [](uint8_t byte) { return byte == 0xff; }); } static QSize parsePhysicalSize(const uint8_t *data) { // Convert physical size from centimeters to millimeters. return QSize(data[0x15], data[0x16]) * 10; } -static QByteArray parseEisaId(const uint8_t *data) +static QByteArray parsePnpId(const uint8_t *data) { - for (int i = 72; i <= 108; i += 18) { - // Skip the block if it isn't used as monitor descriptor. - if (data[i]) { - continue; - } - if (data[i + 1]) { - continue; - } - - // We have found the EISA ID, it's stored as ASCII. - if (data[i + 3] == 0xfe) { - return QByteArray(reinterpret_cast(&data[i + 5]), 12).trimmed(); - } - } - - // If there isn't an ASCII EISA ID descriptor, try to decode PNP ID from - // three 5 bit words packed into 2 bytes: + // Decode PNP ID from three 5 bit words packed into 2 bytes: // // | Byte | Bit | // | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | // ---------------------------------------- // | 1 | 0)| (4| 3 | 2 | 1 | 0)| (4| 3 | // | | * | Character 1 | Char 2| // ---------------------------------------- // | 2 | 2 | 1 | 0)| (4| 3 | 2 | 1 | 0)| // | | Character2| Character 3 | // ---------------------------------------- const uint offset = 0x8; char pnpId[4]; pnpId[0] = 'A' + ((data[offset + 0] >> 2) & 0x1f) - 1; pnpId[1] = 'A' + (((data[offset + 0] & 0x3) << 3) | ((data[offset + 1] >> 5) & 0x7)) - 1; pnpId[2] = 'A' + (data[offset + 1] & 0x1f) - 1; pnpId[3] = '\0'; return QByteArray(pnpId); } + +static QByteArray parseEisaId(const uint8_t *data) +{ + for (int i = 72; i <= 108; i += 18) { + // Skip the block if it isn't used as monitor descriptor. + if (data[i]) { + continue; + } + if (data[i + 1]) { + continue; + } + + // We have found the EISA ID, it's stored as ASCII. + if (data[i + 3] == 0xfe) { + return QByteArray(reinterpret_cast(&data[i + 5]), 12).trimmed(); + } + } + + // If there isn't an ASCII EISA ID descriptor, try to decode PNP ID + return parsePnpId(data); +} + static QByteArray parseMonitorName(const uint8_t *data) { for (int i = 72; i <= 108; i += 18) { // Skip the block if it isn't used as monitor descriptor. if (data[i]) { continue; } if (data[i + 1]) { continue; } // We have found the monitor name, it's stored as ASCII. if (data[i + 3] == 0xfc) { return QByteArray(reinterpret_cast(&data[i + 5]), 12).trimmed(); } } return QByteArray(); } static QByteArray parseSerialNumber(const uint8_t *data) { for (int i = 72; i <= 108; i += 18) { // Skip the block if it isn't used as monitor descriptor. if (data[i]) { continue; } if (data[i + 1]) { continue; } // We have found the serial number, it's stored as ASCII. if (data[i + 3] == 0xff) { return QByteArray(reinterpret_cast(&data[i + 5]), 12).trimmed(); } } // Maybe there isn't an ASCII serial number descriptor, so use this instead. const uint32_t offset = 0xc; uint32_t serialNumber = data[offset + 0]; serialNumber |= uint32_t(data[offset + 1]) << 8; serialNumber |= uint32_t(data[offset + 2]) << 16; serialNumber |= uint32_t(data[offset + 3]) << 24; if (serialNumber) { return QByteArray::number(serialNumber); } return QByteArray(); } +static QByteArray parseVendor(const uint8_t *data) +{ +#if HAVE_HWDATA + const auto pnpId = parsePnpId(data); + + // Map to vendor name + QFile pnpFile(QStringLiteral(HWDATA_PNPIDS_FILE)); + if (pnpFile.exists() && pnpFile.open(QIODevice::ReadOnly)) { + while (!pnpFile.atEnd()) { + const auto line = pnpFile.readLine(); + if (line.startsWith(pnpId)) { + return line.mid(4).trimmed(); + } + } + } +#endif + return {}; +} + Edid::Edid() { } Edid::Edid(const void *data, uint32_t size) { const uint8_t *bytes = static_cast(data); if (size < 128) { return; } if (!verifyHeader(bytes)) { return; } m_physicalSize = parsePhysicalSize(bytes); m_eisaId = parseEisaId(bytes); m_monitorName = parseMonitorName(bytes); m_serialNumber = parseSerialNumber(bytes); + m_vendor = parseVendor(bytes); m_isValid = true; } bool Edid::isValid() const { return m_isValid; } QSize Edid::physicalSize() const { return m_physicalSize; } QByteArray Edid::eisaId() const { return m_eisaId; } QByteArray Edid::monitorName() const { return m_monitorName; } QByteArray Edid::serialNumber() const { return m_serialNumber; } +QByteArray Edid::vendor() const +{ + return m_vendor; +} + } // namespace KWin diff --git a/plugins/platforms/drm/edid.h b/plugins/platforms/drm/edid.h index 15b06b4e1..3b289907c 100644 --- a/plugins/platforms/drm/edid.h +++ b/plugins/platforms/drm/edid.h @@ -1,73 +1,79 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2019 Vlad Zahorodnii This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #pragma once #include #include namespace KWin { /** * Helper class that can be used for parsing EDID blobs. * * http://read.pudn.com/downloads110/ebook/456020/E-EDID%20Standard.pdf */ class Edid { public: Edid(); Edid(const void *data, uint32_t size); /** * Whether this instance of EDID is valid. */ bool isValid() const; /** * Returns physical dimensions of the monitor, in millimeters. */ QSize physicalSize() const; /** * Returns EISA ID of the manufacturer of the monitor. */ QByteArray eisaId() const; /** * Returns the product name of the monitor. */ QByteArray monitorName() const; /** * Returns the serial number of the monitor. */ QByteArray serialNumber() const; + /** + * Returns the name of the vendor. + */ + QByteArray vendor() const; + private: QSize m_physicalSize; + QByteArray m_vendor; QByteArray m_eisaId; QByteArray m_monitorName; QByteArray m_serialNumber; bool m_isValid = false; }; } // namespace KWin