diff --git a/CMakeLists.txt b/CMakeLists.txt index 3de7d8d9c..e645349b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,631 +1,632 @@ project(KWIN) set(PROJECT_VERSION "5.6.90") set(PROJECT_VERSION_MAJOR 5) cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) set(QT_MIN_VERSION "5.4.0") set(KF5_MIN_VERSION "5.12.0") set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH} ) find_package(ECM 0.0.11 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 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(ECMInstallIcons) include(ECMOptionalAddSubdirectory) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0 -DQT_USE_QSTRINGBUILDER) # require at least gcc 4.8 if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") if ("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS "4.8") message(SEND_ERROR "Version ${CMAKE_CXX_COMPILER_VERSION} of the ${CMAKE_CXX_COMPILER_ID} C++ compiler is not supported. Please use version 4.8 or later.") endif() endif() 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 Init Notifications Package Plasma WidgetsAddons WindowSystem IconThemes IdleTime ) # required frameworks by config modules find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Completion Declarative KCMUtils KIO NewStuff Service 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(KDecoration2 CONFIG REQUIRED) find_package(KF5Wayland CONFIG REQUIRED) set_package_properties(KF5Wayland PROPERTIES TYPE REQUIRED ) find_package(KScreenLocker CONFIG REQUIRED) set_package_properties(KScreenLocker PROPERTIES TYPE REQUIRED PURPOSE "For screenlocker integration in kwin_wayland") find_package(Breeze ${PROJECT_VERSION} 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 "http://github.com/anholt/libepoxy" TYPE REQUIRED PURPOSE "OpenGL dispatch library" ) 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.4.1) set_package_properties(XKB PROPERTIES TYPE REQUIRED PURPOSE "Required for building KWin with Wayland support" ) find_package(Libinput 0.10) set_package_properties(Libinput PROPERTIES TYPE OPTIONAL PURPOSE "Required for input handling on Wayland.") find_package(UDev) set_package_properties(UDev PROPERTIES URL "http://www.freedesktop.org/software/systemd/libudev/" DESCRIPTION "Linux device library." TYPE OPTIONAL PURPOSE "Required for input handling on Wayland." ) set(HAVE_INPUT FALSE) if (Libinput_FOUND AND UDEV_FOUND) set(HAVE_INPUT TRUE) endif() find_package(Libdrm) set_package_properties(Libdrm PROPERTIES TYPE OPTIONAL PURPOSE "Required for drm output on Wayland.") set(HAVE_DRM FALSE) if(Libdrm_FOUND AND UDEV_FOUND) set(HAVE_DRM TRUE) endif() find_package(gbm) set_package_properties(gbm PROPERTIES TYPE OPTIONAL PURPOSE "Required for egl ouput of drm backend.") set(HAVE_GBM FALSE) if(HAVE_DRM AND gbm_FOUND) set(HAVE_GBM 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 "http://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 XCB XFIXES DAMAGE COMPOSITE SHAPE SYNC RENDER RANDR KEYSYMS IMAGE SHM GLX CURSOR OPTIONAL_COMPONENTS ICCCM ) 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(Qt5PlatformSupport REQUIRED) find_package(Freetype REQUIRED) set_package_properties(Freetype PROPERTIES DESCRIPTION "A font rendering engine" URL "http://www.freetype.org" TYPE REQUIRED PURPOSE "Needed for KWin's QPA plugin." ) find_package(Fontconfig REQUIRED) set_package_properties(Fontconfig PROPERTIES DESCRIPTION "Font access configuration library" URL "http://www.freedesktop.org/wiki/Software/fontconfig" TYPE REQUIRED PURPOSE "Needed for KWin's QPA plugin." ) find_package(Xwayland) set_package_properties(Xwayland PROPERTIES URL "http://x.org" DESCRIPTION "Xwayland X server" TYPE RUNTIME PURPOSE "Needed for running kwin_wayland" ) ########### 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") set(KWIN_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) # 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) add_feature_info("prctl-dumpable" HAVE_PR_SET_DUMPABLE "Required for disallow ptrace on kwin_wayland process") 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 ) add_subdirectory( libkwineffects ) add_subdirectory( killer ) if(KWIN_BUILD_KCMS) add_subdirectory( kcmkwin ) endif() add_subdirectory( data ) add_subdirectory( effects ) add_subdirectory( scripts ) add_subdirectory( tabbox ) add_subdirectory(scripting) add_definitions(-DKDE_DEFAULT_DEBUG_AREA=1212) ########### next target ############### set(kwin_KDEINIT_SRCS workspace.cpp dbusinterface.cpp abstract_client.cpp client.cpp client_machine.cpp cursor.cpp debug_console.cpp tabgroup.cpp focuschain.cpp globalshortcuts.cpp input.cpp keyboard_input.cpp pointer_input.cpp touch_input.cpp netinfo.cpp placement.cpp atoms.cpp utils.cpp layers.cpp main.cpp options.cpp outline.cpp events.cpp killwindow.cpp geometrytip.cpp screens.cpp shadow.cpp sm.cpp group.cpp manage.cpp overlaywindow.cpp activation.cpp useractions.cpp geometry.cpp rules.cpp composite.cpp toplevel.cpp unmanaged.cpp scene.cpp scene_xrender.cpp scene_opengl.cpp scene_qpainter.cpp thumbnailitem.cpp lanczosfilter.cpp deleted.cpp effects.cpp effectloader.cpp compositingprefs.cpp virtualdesktops.cpp xcbutils.cpp x11eventfilter.cpp logind.cpp screenedge.cpp scripting/scripting.cpp scripting/workspace_wrapper.cpp scripting/meta.cpp scripting/scriptedeffect.cpp scripting/scriptingutils.cpp scripting/timer.cpp scripting/scripting_model.cpp scripting/dbuscall.cpp scripting/screenedgeitem.cpp scripting/scripting_logging.cpp decorations/decoratedclient.cpp decorations/decorationbridge.cpp decorations/decorationpalette.cpp decorations/settings.cpp decorations/decorationrenderer.cpp decorations/decorations_logging.cpp abstract_egl_backend.cpp platform.cpp shell_client.cpp wayland_server.cpp wayland_cursor_theme.cpp ) if(KWIN_BUILD_TABBOX) set( kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} tabbox/tabbox.cpp tabbox/clientmodel.cpp tabbox/desktopchain.cpp tabbox/desktopmodel.cpp tabbox/switcheritem.cpp tabbox/tabboxconfig.cpp tabbox/tabboxhandler.cpp tabbox/tabbox_logging.cpp ) endif() if(KWIN_BUILD_ACTIVITIES) set( kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} activities.cpp ) endif() if(UDEV_FOUND) set(kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} udev.cpp ) endif() if(HAVE_INPUT) set(kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} libinput/context.cpp libinput/connection.cpp + libinput/device.cpp libinput/events.cpp libinput/libinput_logging.cpp virtual_terminal.cpp ) endif() kconfig_add_kcfg_files(kwin_KDEINIT_SRCS settings.kcfgc) qt5_add_dbus_adaptor( kwin_KDEINIT_SRCS org.kde.KWin.xml dbusinterface.h KWin::DBusInterface ) qt5_add_dbus_adaptor( kwin_KDEINIT_SRCS org.kde.kwin.Compositing.xml dbusinterface.h KWin::CompositorDBusInterface ) qt5_add_dbus_adaptor( kwin_KDEINIT_SRCS ${kwin_effects_dbus_xml} effects.h KWin::EffectsHandlerImpl ) qt5_add_dbus_interface( kwin_KDEINIT_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/org.freedesktop.ScreenSaver.xml screenlocker_interface) qt5_add_resources( kwin_KDEINIT_SRCS resources.qrc ) ki18n_wrap_ui(kwin_KDEINIT_SRCS debug_console.ui shortcutdialog.ui ) ########### target link libraries ############### set(kwin_OWN_LIBS kwineffects kwinxrenderutils kwin4_effect_builtins ) set(kwin_QT_LIBS Qt5::Concurrent Qt5::DBus Qt5::Quick Qt5::Script Qt5::X11Extras ) set(kwin_KDE_LIBS KF5::ConfigCore KF5::CoreAddons KF5::ConfigWidgets KF5::Crash KF5::GlobalAccel KF5::GlobalAccelPrivate KF5::I18n KF5::Notifications KF5::Package KF5::Plasma KF5::WindowSystem KDecoration2::KDecoration KDecoration2::KDecoration2Private PW::KScreenLocker ) set(kwin_XLIB_LIBS ${X11_X11_LIB} ${X11_ICE_LIB} ${X11_SM_LIB} ) if(X11_Xinput_FOUND) set(kwin_XLIB_LIBS ${kwin_XLIB_LIBS} ${X11_Xinput_LIB}) endif() set(kwin_XCB_LIBS XCB::XCB XCB::XFIXES XCB::DAMAGE XCB::COMPOSITE XCB::SHAPE XCB::SYNC XCB::RENDER XCB::RANDR XCB::KEYSYMS XCB::SHM XCB::GLX XCB::CURSOR ) set(kwin_WAYLAND_LIBS XKB::XKB KF5::WaylandClient KF5::WaylandServer Wayland::Cursor ${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} ) if(UDEV_FOUND) set(kwinLibs ${kwinLibs} ${UDEV_LIBS}) endif() if(HAVE_INPUT) set(kwinLibs ${kwinLibs} Libinput::Libinput) endif() add_library(kwin SHARED ${kwin_KDEINIT_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}) # -ldl used by OpenGL code find_library(DL_LIBRARY dl) if (DL_LIBRARY) target_link_libraries(kwin ${DL_LIBRARY}) endif() kf5_add_kdeinit_executable(kwin_x11 main_x11.cpp) target_link_libraries(kdeinit_kwin_x11 kwin) install(TARGETS kwin ${INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP ) install(TARGETS kdeinit_kwin_x11 ${INSTALL_TARGETS_DEFAULT_ARGS} ) install(TARGETS kwin_x11 ${INSTALL_TARGETS_DEFAULT_ARGS} ) add_executable(kwin_wayland main_wayland.cpp) target_link_libraries(kwin_wayland kwin) install(TARGETS kwin_wayland ${INSTALL_TARGETS_DEFAULT_ARGS} ) add_subdirectory(plugins) ########### install files ############### install( FILES kwin.kcfg DESTINATION ${KCFG_INSTALL_DIR} RENAME ${KWIN_NAME}.kcfg ) install( FILES kwin.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR} RENAME ${KWIN_NAME}.notifyrc) install( FILES org.kde.KWin.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} ) ecm_install_icons( ICONS 16-apps-kwin.png 32-apps-kwin.png 48-apps-kwin.png sc-apps-kwin.svgz DESTINATION ${ICON_INSTALL_DIR} THEME hicolor ) add_subdirectory(qml) add_subdirectory(autotests) add_subdirectory(tests) if (KF5DocTools_FOUND) add_subdirectory(doc) endif() feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) include(ECMPackageConfigHelpers) set(CMAKECONFIG_INSTALL_DIR "${CMAKECONFIG_INSTALL_PREFIX}/KWinDBusInterface") ecm_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/debug_console.cpp b/debug_console.cpp index 3593effd0..7151784c2 100644 --- a/debug_console.cpp +++ b/debug_console.cpp @@ -1,1016 +1,1220 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 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 "debug_console.h" #include "client.h" #include "main.h" #include "shell_client.h" #include "unmanaged.h" #include "wayland_server.h" #include "workspace.h" +#if HAVE_INPUT +#include "libinput/connection.h" +#include "libinput/device.h" +#endif #include "ui_debug_console.h" // KWayland #include #include #include #include // frameworks #include #include // Qt #include #include #include namespace KWin { static QString tableHeaderRow(const QString &title) { return QStringLiteral("%1").arg(title); } template static QString tableRow(const QString &title, const T &argument) { return QStringLiteral("%1%2").arg(title).arg(argument); } static QString timestampRow(quint32 timestamp) { return tableRow(i18n("Timestamp"), timestamp); } static QString buttonToString(Qt::MouseButton button) { switch (button) { case Qt::LeftButton: return i18nc("A mouse button", "Left"); case Qt::RightButton: return i18nc("A mouse button", "Right"); case Qt::MiddleButton: return i18nc("A mouse button", "Middle"); case Qt::BackButton: return i18nc("A mouse button", "Back"); case Qt::ForwardButton: return i18nc("A mouse button", "Forward"); case Qt::TaskButton: return i18nc("A mouse button", "Task"); case Qt::ExtraButton4: return i18nc("A mouse button", "Extra Button 4"); case Qt::ExtraButton5: return i18nc("A mouse button", "Extra Button 5"); case Qt::ExtraButton6: return i18nc("A mouse button", "Extra Button 6"); case Qt::ExtraButton7: return i18nc("A mouse button", "Extra Button 7"); case Qt::ExtraButton8: return i18nc("A mouse button", "Extra Button 8"); case Qt::ExtraButton9: return i18nc("A mouse button", "Extra Button 9"); case Qt::ExtraButton10: return i18nc("A mouse button", "Extra Button 10"); case Qt::ExtraButton11: return i18nc("A mouse button", "Extra Button 11"); case Qt::ExtraButton12: return i18nc("A mouse button", "Extra Button 12"); case Qt::ExtraButton13: return i18nc("A mouse button", "Extra Button 13"); case Qt::ExtraButton14: return i18nc("A mouse button", "Extra Button 14"); case Qt::ExtraButton15: return i18nc("A mouse button", "Extra Button 15"); case Qt::ExtraButton16: return i18nc("A mouse button", "Extra Button 16"); case Qt::ExtraButton17: return i18nc("A mouse button", "Extra Button 17"); case Qt::ExtraButton18: return i18nc("A mouse button", "Extra Button 18"); case Qt::ExtraButton19: return i18nc("A mouse button", "Extra Button 19"); case Qt::ExtraButton20: return i18nc("A mouse button", "Extra Button 20"); case Qt::ExtraButton21: return i18nc("A mouse button", "Extra Button 21"); case Qt::ExtraButton22: return i18nc("A mouse button", "Extra Button 22"); case Qt::ExtraButton23: return i18nc("A mouse button", "Extra Button 23"); case Qt::ExtraButton24: return i18nc("A mouse button", "Extra Button 24"); default: return QString(); } } static QString buttonsToString(Qt::MouseButtons buttons) { QString ret; for (uint i = 1; i < Qt::ExtraButton24; i = i << 1) { if (buttons & i) { ret.append(buttonToString(Qt::MouseButton(uint(buttons) & i))); ret.append(QStringLiteral(" ")); } }; return ret; } static const QString s_hr = QStringLiteral("
"); static const QString s_tableStart = QStringLiteral(""); static const QString s_tableEnd = QStringLiteral("
"); DebugConsoleFilter::DebugConsoleFilter(QTextEdit *textEdit) : InputEventFilter() , m_textEdit(textEdit) { } DebugConsoleFilter::~DebugConsoleFilter() = default; bool DebugConsoleFilter::pointerEvent(QMouseEvent *event, quint32 nativeButton) { QString text = s_hr; const QString timestamp = timestampRow(event->timestamp()); text.append(s_tableStart); switch (event->type()) { case QEvent::MouseMove: text.append(tableHeaderRow(i18nc("A mouse pointer motion event", "Pointer Motion"))); text.append(timestamp); text.append(tableRow(i18nc("The global mouse pointer position", "Global Position"), QStringLiteral("%1/%2").arg(event->pos().x()).arg(event->pos().y()))); break; case QEvent::MouseButtonPress: text.append(tableHeaderRow(i18nc("A mouse pointer button press event", "Pointer Button Press"))); text.append(timestamp); text.append(tableRow(i18nc("A button in a mouse press/release event", "Button"), buttonToString(event->button()))); text.append(tableRow(i18nc("A button in a mouse press/release event", "Native Button code"), nativeButton)); text.append(tableRow(i18nc("All currently pressed buttons in a mouse press/release event", "Pressed Buttons"), buttonsToString(event->buttons()))); break; case QEvent::MouseButtonRelease: text.append(tableHeaderRow(i18nc("A mouse pointer button release event", "Pointer Button Release"))); text.append(timestamp); text.append(tableRow(i18nc("A button in a mouse press/release event", "Button"), buttonToString(event->button()))); text.append(tableRow(i18nc("A button in a mouse press/release event", "Native Button code"), nativeButton)); text.append(tableRow(i18nc("All currently pressed buttons in a mouse press/release event", "Pressed Buttons"), buttonsToString(event->buttons()))); break; default: break; } text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); return false; } bool DebugConsoleFilter::wheelEvent(QWheelEvent *event) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A mouse pointer axis (wheel) event", "Pointer Axis"))); text.append(timestampRow(event->timestamp())); const Qt::Orientation orientation = event->angleDelta().x() == 0 ? Qt::Vertical : Qt::Horizontal; text.append(tableRow(i18nc("The orientation of a pointer axis event", "Orientation"), orientation == Qt::Horizontal ? i18nc("An orientation of a pointer axis event", "Horizontal") : i18nc("An orientation of a pointer axis event", "Vertical"))); text.append(tableRow(QStringLiteral("Delta"), orientation == Qt::Horizontal ? event->angleDelta().x() : event->angleDelta().y())); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); return false; } bool DebugConsoleFilter::keyEvent(QKeyEvent *event) { QString text = s_hr; text.append(s_tableStart); switch (event->type()) { case QEvent::KeyPress: text.append(tableHeaderRow(i18nc("A key press event", "Key Press"))); break; case QEvent::KeyRelease: text.append(tableHeaderRow(i18nc("A key release event", "Key Release"))); break; default: break; } auto modifiersToString = [event] { QString ret; if (event->modifiers().testFlag(Qt::ShiftModifier)) { ret.append(i18nc("A keyboard modifier", "Shift")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::ControlModifier)) { ret.append(i18nc("A keyboard modifier", "Control")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::AltModifier)) { ret.append(i18nc("A keyboard modifier", "Alt")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::MetaModifier)) { ret.append(i18nc("A keyboard modifier", "Meta")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::KeypadModifier)) { ret.append(i18nc("A keyboard modifier", "Keypad")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::GroupSwitchModifier)) { ret.append(i18nc("A keyboard modifier", "Group-switch")); ret.append(QStringLiteral(" ")); } return ret; }; text.append(timestampRow(event->timestamp())); text.append(tableRow(i18nc("Whether the event is an automatic key repeat", "Repeat"), event->isAutoRepeat())); text.append(tableRow(i18nc("The code as read from the input device", "Scan code"), event->nativeScanCode())); text.append(tableRow(i18nc("The translated code to an Xkb symbol", "Xkb symbol"), event->nativeVirtualKey())); text.append(tableRow(i18nc("The translated code interpreted as text", "Utf8"), event->text())); text.append(tableRow(i18nc("The currently active modifiers", "Modifiers"), modifiersToString())); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); return false; } bool DebugConsoleFilter::touchDown(quint32 id, const QPointF &pos, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A touch down event", "Touch down"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("The id of the touch point in the touch event", "Point identifier"), id)); text.append(tableRow(i18nc("The global position of the touch point", "Global position"), QStringLiteral("%1/%2").arg(pos.x()).arg(pos.y()))); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); return false; } bool DebugConsoleFilter::touchMotion(quint32 id, const QPointF &pos, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A touch motion event", "Touch Motion"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("The id of the touch point in the touch event", "Point identifier"), id)); text.append(tableRow(i18nc("The global position of the touch point", "Global position"), QStringLiteral("%1/%2").arg(pos.x()).arg(pos.y()))); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); return false; } bool DebugConsoleFilter::touchUp(quint32 id, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A touch up event", "Touch Up"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("The id of the touch point in the touch event", "Point identifier"), id)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); return false; } DebugConsole::DebugConsole() : QWidget() , m_ui(new Ui::DebugConsole) { m_ui->setupUi(this); m_ui->windowsView->setItemDelegate(new DebugConsoleDelegate(this)); m_ui->windowsView->setModel(new DebugConsoleModel(this)); m_ui->surfacesView->setModel(new SurfaceTreeModel(this)); +#if HAVE_INPUT + if (kwinApp()->usesLibinput()) { + m_ui->inputDevicesView->setModel(new InputDeviceModel(this)); + m_ui->inputDevicesView->setItemDelegate(new DebugConsoleDelegate(this)); + } +#endif m_ui->quitButton->setIcon(QIcon::fromTheme(QStringLiteral("application-exit"))); m_ui->tabWidget->setTabIcon(0, QIcon::fromTheme(QStringLiteral("view-list-tree"))); m_ui->tabWidget->setTabIcon(1, QIcon::fromTheme(QStringLiteral("view-list-tree"))); if (kwinApp()->operationMode() == Application::OperationMode::OperationModeX11) { m_ui->tabWidget->setTabEnabled(1, false); m_ui->tabWidget->setTabEnabled(2, false); } + if (!kwinApp()->usesLibinput()) { + m_ui->tabWidget->setTabEnabled(3, false); + } connect(m_ui->quitButton, &QAbstractButton::clicked, this, &DebugConsole::deleteLater); connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, [this] (int index) { // delay creation of input event filter until the tab is selected if (index == 2 && m_inputFilter.isNull()) { m_inputFilter.reset(new DebugConsoleFilter(m_ui->inputTextEdit)); input()->prepandInputEventFilter(m_inputFilter.data()); } } ); // for X11 setWindowFlags(Qt::X11BypassWindowManagerHint); } DebugConsole::~DebugConsole() = default; DebugConsoleDelegate::DebugConsoleDelegate(QObject *parent) : QStyledItemDelegate(parent) { } DebugConsoleDelegate::~DebugConsoleDelegate() = default; QString DebugConsoleDelegate::displayText(const QVariant &value, const QLocale &locale) const { switch (value.type()) { case QMetaType::QPoint: { const QPoint p = value.toPoint(); return QStringLiteral("%1,%2").arg(p.x()).arg(p.y()); } case QMetaType::QPointF: { const QPointF p = value.toPointF(); return QStringLiteral("%1,%2").arg(p.x()).arg(p.y()); } case QMetaType::QSize: { const QSize s = value.toSize(); return QStringLiteral("%1x%2").arg(s.width()).arg(s.height()); } case QMetaType::QSizeF: { const QSizeF s = value.toSizeF(); return QStringLiteral("%1x%2").arg(s.width()).arg(s.height()); } case QMetaType::QRect: { const QRect r = value.toRect(); return QStringLiteral("%1,%2 %3x%4").arg(r.x()).arg(r.y()).arg(r.width()).arg(r.height()); } default: if (value.userType() == qMetaTypeId()) { if (auto s = value.value()) { return QStringLiteral("KWayland::Server::SurfaceInterface(0x%1)").arg(qulonglong(s), 0, 16); } else { return QStringLiteral("nullptr"); } } + if (value.userType() == qMetaTypeId()) { + const auto buttons = value.value(); + if (buttons == Qt::NoButton) { + return i18n("No Mouse Buttons"); + } + QStringList list; + if (buttons.testFlag(Qt::LeftButton)) { + list << i18nc("Mouse Button", "left"); + } + if (buttons.testFlag(Qt::RightButton)) { + list << i18nc("Mouse Button", "right"); + } + if (buttons.testFlag(Qt::MiddleButton)) { + list << i18nc("Mouse Button", "middle"); + } + if (buttons.testFlag(Qt::BackButton)) { + list << i18nc("Mouse Button", "back"); + } + if (buttons.testFlag(Qt::ForwardButton)) { + list << i18nc("Mouse Button", "forward"); + } + if (buttons.testFlag(Qt::ExtraButton1)) { + list << i18nc("Mouse Button", "extra 1"); + } + if (buttons.testFlag(Qt::ExtraButton2)) { + list << i18nc("Mouse Button", "extra 2"); + } + if (buttons.testFlag(Qt::ExtraButton3)) { + list << i18nc("Mouse Button", "extra 3"); + } + if (buttons.testFlag(Qt::ExtraButton4)) { + list << i18nc("Mouse Button", "extra 4"); + } + if (buttons.testFlag(Qt::ExtraButton5)) { + list << i18nc("Mouse Button", "extra 5"); + } + if (buttons.testFlag(Qt::ExtraButton6)) { + list << i18nc("Mouse Button", "extra 6"); + } + if (buttons.testFlag(Qt::ExtraButton7)) { + list << i18nc("Mouse Button", "extra 7"); + } + if (buttons.testFlag(Qt::ExtraButton8)) { + list << i18nc("Mouse Button", "extra 8"); + } + if (buttons.testFlag(Qt::ExtraButton9)) { + list << i18nc("Mouse Button", "extra 9"); + } + if (buttons.testFlag(Qt::ExtraButton10)) { + list << i18nc("Mouse Button", "extra 10"); + } + if (buttons.testFlag(Qt::ExtraButton11)) { + list << i18nc("Mouse Button", "extra 11"); + } + if (buttons.testFlag(Qt::ExtraButton12)) { + list << i18nc("Mouse Button", "extra 12"); + } + if (buttons.testFlag(Qt::ExtraButton13)) { + list << i18nc("Mouse Button", "extra 13"); + } + if (buttons.testFlag(Qt::ExtraButton14)) { + list << i18nc("Mouse Button", "extra 14"); + } + if (buttons.testFlag(Qt::ExtraButton15)) { + list << i18nc("Mouse Button", "extra 15"); + } + if (buttons.testFlag(Qt::ExtraButton16)) { + list << i18nc("Mouse Button", "extra 16"); + } + if (buttons.testFlag(Qt::ExtraButton17)) { + list << i18nc("Mouse Button", "extra 17"); + } + if (buttons.testFlag(Qt::ExtraButton18)) { + list << i18nc("Mouse Button", "extra 18"); + } + if (buttons.testFlag(Qt::ExtraButton19)) { + list << i18nc("Mouse Button", "extra 19"); + } + if (buttons.testFlag(Qt::ExtraButton20)) { + list << i18nc("Mouse Button", "extra 20"); + } + if (buttons.testFlag(Qt::ExtraButton21)) { + list << i18nc("Mouse Button", "extra 21"); + } + if (buttons.testFlag(Qt::ExtraButton22)) { + list << i18nc("Mouse Button", "extra 22"); + } + if (buttons.testFlag(Qt::ExtraButton23)) { + list << i18nc("Mouse Button", "extra 23"); + } + if (buttons.testFlag(Qt::ExtraButton24)) { + list << i18nc("Mouse Button", "extra 24"); + } + if (buttons.testFlag(Qt::TaskButton)) { + list << i18nc("Mouse Button", "task"); + } + return list.join(QStringLiteral(", ")); + } break; } return QStyledItemDelegate::displayText(value, locale); } static const int s_x11ClientId = 1; static const int s_x11UnmanagedId = 2; static const int s_waylandClientId = 3; static const int s_waylandInternalId = 4; static const quint32 s_propertyBitMask = 0xFFFF0000; static const quint32 s_clientBitMask = 0x0000FFFF; static const quint32 s_idDistance = 10000; template void DebugConsoleModel::add(int parentRow, QVector &clients, T *client) { beginInsertRows(index(parentRow, 0, QModelIndex()), clients.count(), clients.count()); clients.append(client); endInsertRows(); } template void DebugConsoleModel::remove(int parentRow, QVector &clients, T *client) { const int remove = clients.indexOf(client); if (remove == -1) { return; } beginRemoveRows(index(parentRow, 0, QModelIndex()), remove, remove); clients.removeAt(remove); endRemoveRows(); } DebugConsoleModel::DebugConsoleModel(QObject *parent) : QAbstractItemModel(parent) { if (waylandServer()) { const auto clients = waylandServer()->clients(); for (auto c : clients) { m_shellClients.append(c); } const auto internals = waylandServer()->internalClients(); for (auto c : internals) { m_internalClients.append(c); } // TODO: that only includes windows getting shown, not those which are only created connect(waylandServer(), &WaylandServer::shellClientAdded, this, [this] (ShellClient *c) { if (c->isInternal()) { add(s_waylandInternalId -1, m_internalClients, c); } else { add(s_waylandClientId -1, m_shellClients, c); } } ); connect(waylandServer(), &WaylandServer::shellClientRemoved, this, [this] (ShellClient *c) { remove(s_waylandInternalId -1, m_internalClients, c); remove(s_waylandClientId -1, m_shellClients, c); } ); } const auto x11Clients = workspace()->clientList(); for (auto c : x11Clients) { m_x11Clients.append(c); } const auto x11DesktopClients = workspace()->desktopList(); for (auto c : x11DesktopClients) { m_x11Clients.append(c); } connect(workspace(), &Workspace::clientAdded, this, [this] (Client *c) { add(s_x11ClientId -1, m_x11Clients, c); } ); connect(workspace(), &Workspace::clientRemoved, this, [this] (AbstractClient *ac) { Client *c = qobject_cast(ac); if (!c) { return; } remove(s_x11ClientId -1, m_x11Clients, c); } ); const auto unmangeds = workspace()->unmanagedList(); for (auto u : unmangeds) { m_unmanageds.append(u); } connect(workspace(), &Workspace::unmanagedAdded, this, [this] (Unmanaged *u) { add(s_x11UnmanagedId -1, m_unmanageds, u); } ); connect(workspace(), &Workspace::unmanagedRemoved, this, [this] (Unmanaged *u) { remove(s_x11UnmanagedId -1, m_unmanageds, u); } ); } DebugConsoleModel::~DebugConsoleModel() = default; int DebugConsoleModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 2; } int DebugConsoleModel::topLevelRowCount() const { return kwinApp()->shouldUseWaylandForCompositing() ? 4 : 2; } template int DebugConsoleModel::propertyCount(const QModelIndex &parent, T *(DebugConsoleModel::*filter)(const QModelIndex&) const) const { if (T *t = (this->*filter)(parent)) { return t->metaObject()->propertyCount(); } return 0; } int DebugConsoleModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return topLevelRowCount(); } switch (parent.internalId()) { case s_x11ClientId: return m_x11Clients.count(); case s_x11UnmanagedId: return m_unmanageds.count(); case s_waylandClientId: return m_shellClients.count(); case s_waylandInternalId: return m_internalClients.count(); default: break; } if (parent.internalId() & s_propertyBitMask) { // properties do not have children return 0; } if (parent.internalId() < s_idDistance * (s_x11ClientId + 1)) { return propertyCount(parent, &DebugConsoleModel::x11Client); } else if (parent.internalId() < s_idDistance * (s_x11UnmanagedId + 1)) { return propertyCount(parent, &DebugConsoleModel::unmanaged); } else if (parent.internalId() < s_idDistance * (s_waylandClientId + 1)) { return propertyCount(parent, &DebugConsoleModel::shellClient); } else if (parent.internalId() < s_idDistance * (s_waylandInternalId + 1)) { return propertyCount(parent, &DebugConsoleModel::internalClient); } return 0; } template QModelIndex DebugConsoleModel::indexForClient(int row, int column, const QVector &clients, int id) const { if (column != 0) { return QModelIndex(); } if (row >= clients.count()) { return QModelIndex(); } return createIndex(row, column, s_idDistance * id + row); } template QModelIndex DebugConsoleModel::indexForProperty(int row, int column, const QModelIndex &parent, T *(DebugConsoleModel::*filter)(const QModelIndex&) const) const { if (T *t = (this->*filter)(parent)) { if (row >= t->metaObject()->propertyCount()) { return QModelIndex(); } return createIndex(row, column, quint32(row + 1) << 16 | parent.internalId()); } return QModelIndex(); } QModelIndex DebugConsoleModel::index(int row, int column, const QModelIndex &parent) const { if (!parent.isValid()) { // index for a top level item if (column != 0 || row >= topLevelRowCount()) { return QModelIndex(); } return createIndex(row, column, row + 1); } if (column >= 2) { // max of 2 columns return QModelIndex(); } // index for a client (second level) switch (parent.internalId()) { case s_x11ClientId: return indexForClient(row, column, m_x11Clients, s_x11ClientId); case s_x11UnmanagedId: return indexForClient(row, column, m_unmanageds, s_x11UnmanagedId); case s_waylandClientId: return indexForClient(row, column, m_shellClients, s_waylandClientId); case s_waylandInternalId: return indexForClient(row, column, m_internalClients, s_waylandInternalId); default: break; } // index for a property (third level) if (parent.internalId() < s_idDistance * (s_x11ClientId + 1)) { return indexForProperty(row, column, parent, &DebugConsoleModel::x11Client); } else if (parent.internalId() < s_idDistance * (s_x11UnmanagedId + 1)) { return indexForProperty(row, column, parent, &DebugConsoleModel::unmanaged); } else if (parent.internalId() < s_idDistance * (s_waylandClientId + 1)) { return indexForProperty(row, column, parent, &DebugConsoleModel::shellClient); } else if (parent.internalId() < s_idDistance * (s_waylandInternalId + 1)) { return indexForProperty(row, column, parent, &DebugConsoleModel::internalClient); } return QModelIndex(); } QModelIndex DebugConsoleModel::parent(const QModelIndex &child) const { if (child.internalId() <= s_waylandInternalId) { return QModelIndex(); } if (child.internalId() & s_propertyBitMask) { // a property const quint32 parentId = child.internalId() & s_clientBitMask; if (parentId < s_idDistance * (s_x11ClientId + 1)) { return createIndex(parentId - (s_idDistance * s_x11ClientId), 0, parentId); } else if (parentId < s_idDistance * (s_x11UnmanagedId + 1)) { return createIndex(parentId - (s_idDistance * s_x11UnmanagedId), 0, parentId); } else if (parentId < s_idDistance * (s_waylandClientId + 1)) { return createIndex(parentId - (s_idDistance * s_waylandClientId), 0, parentId); } else if (parentId < s_idDistance * (s_waylandInternalId + 1)) { return createIndex(parentId - (s_idDistance * s_waylandInternalId), 0, parentId); } return QModelIndex(); } if (child.internalId() < s_idDistance * (s_x11ClientId + 1)) { return createIndex(s_x11ClientId -1, 0, s_x11ClientId); } else if (child.internalId() < s_idDistance * (s_x11UnmanagedId + 1)) { return createIndex(s_x11UnmanagedId -1, 0, s_x11UnmanagedId); } else if (child.internalId() < s_idDistance * (s_waylandClientId + 1)) { return createIndex(s_waylandClientId -1, 0, s_waylandClientId); } else if (child.internalId() < s_idDistance * (s_waylandInternalId + 1)) { return createIndex(s_waylandInternalId -1, 0, s_waylandInternalId); } return QModelIndex(); } QVariant DebugConsoleModel::propertyData(QObject *object, const QModelIndex &index, int role) const { Q_UNUSED(role) const auto property = object->metaObject()->property(index.row()); if (index.column() == 0) { return property.name(); } else { const QVariant value = property.read(object); if (qstrcmp(property.name(), "windowType") == 0) { switch (value.toInt()) { case NET::Normal: return QStringLiteral("NET::Normal"); case NET::Desktop: return QStringLiteral("NET::Desktop"); case NET::Dock: return QStringLiteral("NET::Dock"); case NET::Toolbar: return QStringLiteral("NET::Toolbar"); case NET::Menu: return QStringLiteral("NET::Menu"); case NET::Dialog: return QStringLiteral("NET::Dialog"); case NET::Override: return QStringLiteral("NET::Override"); case NET::TopMenu: return QStringLiteral("NET::TopMenu"); case NET::Utility: return QStringLiteral("NET::Utility"); case NET::Splash: return QStringLiteral("NET::Splash"); case NET::DropdownMenu: return QStringLiteral("NET::DropdownMenu"); case NET::PopupMenu: return QStringLiteral("NET::PopupMenu"); case NET::Tooltip: return QStringLiteral("NET::Tooltip"); case NET::Notification: return QStringLiteral("NET::Notification"); case NET::ComboBox: return QStringLiteral("NET::ComboBox"); case NET::DNDIcon: return QStringLiteral("NET::DNDIcon"); case NET::OnScreenDisplay: return QStringLiteral("NET::OnScreenDisplay"); case NET::Unknown: default: return QStringLiteral("NET::Unknown"); } } return value; } return QVariant(); } template QVariant DebugConsoleModel::clientData(const QModelIndex &index, int role, const QVector clients) const { if (index.row() >= clients.count()) { return QVariant(); } auto c = clients.at(index.row()); if (role == Qt::DisplayRole) { return QStringLiteral("%1: %2").arg(c->window()).arg(c->caption()); } else if (role == Qt::DecorationRole) { return c->icon(); } return QVariant(); } QVariant DebugConsoleModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (!index.parent().isValid()) { // one of the top levels if (index.column() != 0 || role != Qt::DisplayRole) { return QVariant(); } switch (index.internalId()) { case s_x11ClientId: return i18n("X11 Client Windows"); case s_x11UnmanagedId: return i18n("X11 Unmanaged Windows"); case s_waylandClientId: return i18n("Wayland Windows"); case s_waylandInternalId: return i18n("Internal Windows"); default: return QVariant(); } } if (index.internalId() & s_propertyBitMask) { if (index.column() >= 2 || role != Qt::DisplayRole) { return QVariant(); } if (ShellClient *c = shellClient(index)) { return propertyData(c, index, role); } else if (ShellClient *c = internalClient(index)) { return propertyData(c, index, role); } else if (Client *c = x11Client(index)) { return propertyData(c, index, role); } else if (Unmanaged *u = unmanaged(index)) { return propertyData(u, index, role); } } else { if (index.column() != 0) { return QVariant(); } switch (index.parent().internalId()) { case s_x11ClientId: return clientData(index, role, m_x11Clients); case s_x11UnmanagedId: { if (index.row() >= m_unmanageds.count()) { return QVariant(); } auto u = m_unmanageds.at(index.row()); if (role == Qt::DisplayRole) { return u->window(); } break; } case s_waylandClientId: return clientData(index, role, m_shellClients); case s_waylandInternalId: return clientData(index, role, m_internalClients); default: break; } } return QVariant(); } template static T *clientForIndex(const QModelIndex &index, const QVector &clients, int id) { const qint32 row = (index.internalId() & s_clientBitMask) - (s_idDistance * id); if (row < 0 || row >= clients.count()) { return nullptr; } return clients.at(row); } ShellClient *DebugConsoleModel::shellClient(const QModelIndex &index) const { return clientForIndex(index, m_shellClients, s_waylandClientId); } ShellClient *DebugConsoleModel::internalClient(const QModelIndex &index) const { return clientForIndex(index, m_internalClients, s_waylandInternalId); } Client *DebugConsoleModel::x11Client(const QModelIndex &index) const { return clientForIndex(index, m_x11Clients, s_x11ClientId); } Unmanaged *DebugConsoleModel::unmanaged(const QModelIndex &index) const { return clientForIndex(index, m_unmanageds, s_x11UnmanagedId); } /////////////////////////////////////// SurfaceTreeModel SurfaceTreeModel::SurfaceTreeModel(QObject *parent) : QAbstractItemModel(parent) { // TODO: it would be nice to not have to reset the model on each change auto reset = [this] { beginResetModel(); endResetModel(); }; using namespace KWayland::Server; const auto unmangeds = workspace()->unmanagedList(); for (auto u : unmangeds) { if (!u->surface()) { continue; } connect(u->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } for (auto c : workspace()->allClientList()) { if (!c->surface()) { continue; } connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } for (auto c : workspace()->desktopList()) { if (!c->surface()) { continue; } connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } if (waylandServer()) { for (auto c : waylandServer()->internalClients()) { connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } connect(waylandServer(), &WaylandServer::shellClientAdded, this, [this, reset] (ShellClient *c) { connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); reset(); } ); } connect(workspace(), &Workspace::clientAdded, this, [this, reset] (AbstractClient *c) { if (c->surface()) { connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } reset(); } ); connect(workspace(), &Workspace::clientRemoved, this, reset); connect(workspace(), &Workspace::unmanagedAdded, this, [this, reset] (Unmanaged *u) { if (u->surface()) { connect(u->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } reset(); } ); connect(workspace(), &Workspace::unmanagedRemoved, this, reset); } SurfaceTreeModel::~SurfaceTreeModel() = default; int SurfaceTreeModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 1; } int SurfaceTreeModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { using namespace KWayland::Server; if (SurfaceInterface *surface = static_cast(parent.internalPointer())) { const auto &children = surface->childSubSurfaces(); return children.count(); } return 0; } const int internalClientsCount = waylandServer() ? waylandServer()->internalClients().count() : 0; // toplevel are all windows return workspace()->allClientList().count() + workspace()->desktopList().count() + workspace()->unmanagedList().count() + internalClientsCount; } QModelIndex SurfaceTreeModel::index(int row, int column, const QModelIndex &parent) const { if (column != 0) { // invalid column return QModelIndex(); } if (parent.isValid()) { using namespace KWayland::Server; if (SurfaceInterface *surface = static_cast(parent.internalPointer())) { const auto &children = surface->childSubSurfaces(); if (row < children.count()) { return createIndex(row, column, children.at(row)->surface().data()); } } return QModelIndex(); } // a window const auto &allClients = workspace()->allClientList(); if (row < allClients.count()) { // references a client return createIndex(row, column, allClients.at(row)->surface()); } int reference = allClients.count(); const auto &desktopClients = workspace()->desktopList(); if (row < reference + desktopClients.count()) { return createIndex(row, column, desktopClients.at(row-reference)->surface()); } reference += desktopClients.count(); const auto &unmanaged = workspace()->unmanagedList(); if (row < reference + unmanaged.count()) { return createIndex(row, column, unmanaged.at(row-reference)->surface()); } reference += unmanaged.count(); if (waylandServer()) { const auto &internal = waylandServer()->internalClients(); if (row < reference + internal.count()) { return createIndex(row, column, internal.at(row-reference)->surface()); } } // not found return QModelIndex(); } QModelIndex SurfaceTreeModel::parent(const QModelIndex &child) const { using namespace KWayland::Server; if (SurfaceInterface *surface = static_cast(child.internalPointer())) { const auto &subsurface = surface->subSurface(); if (subsurface.isNull()) { // doesn't reference a subsurface, this is a top-level window return QModelIndex(); } SurfaceInterface *parent = subsurface->parentSurface().data(); if (!parent) { // something is wrong return QModelIndex(); } // is the parent a subsurface itself? if (parent->subSurface()) { auto grandParent = parent->subSurface()->parentSurface(); if (grandParent.isNull()) { // something is wrong return QModelIndex(); } const auto &children = grandParent->childSubSurfaces(); for (int row = 0; row < children.count(); row++) { if (children.at(row).data() == parent->subSurface().data()) { return createIndex(row, 0, parent); } } return QModelIndex(); } // not a subsurface, thus it's a true window int row = 0; const auto &allClients = workspace()->allClientList(); for (; row < allClients.count(); row++) { if (allClients.at(row)->surface() == parent) { return createIndex(row, 0, parent); } } row = allClients.count(); const auto &desktopClients = workspace()->desktopList(); for (int i = 0; i < desktopClients.count(); i++) { if (desktopClients.at(i)->surface() == parent) { return createIndex(row + i, 0, parent); } } row += desktopClients.count(); const auto &unmanaged = workspace()->unmanagedList(); for (int i = 0; i < unmanaged.count(); i++) { if (unmanaged.at(i)->surface() == parent) { return createIndex(row + i, 0, parent); } } row += unmanaged.count(); if (waylandServer()) { const auto &internal = waylandServer()->internalClients(); for (int i = 0; i < internal.count(); i++) { if (internal.at(i)->surface() == parent) { return createIndex(row + i, 0, parent); } } } } return QModelIndex(); } QVariant SurfaceTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } using namespace KWayland::Server; if (SurfaceInterface *surface = static_cast(index.internalPointer())) { if (role == Qt::DisplayRole || role == Qt::ToolTipRole) { return QStringLiteral("%1 (%2) - %3").arg(surface->client()->executablePath()) .arg(surface->client()->processId()) .arg(surface->id()); } else if (role == Qt::DecorationRole) { if (auto buffer = surface->buffer()) { if (buffer->shmBuffer()) { return buffer->data().scaled(QSize(64, 64), Qt::KeepAspectRatio); } } } } return QVariant(); } +#if HAVE_INPUT +InputDeviceModel::InputDeviceModel(QObject *parent) + : QAbstractItemModel(parent) +{ + auto c = LibInput::Connection::self(); + auto resetModel = [this] { + beginResetModel(); + endResetModel(); + }; + connect(c, &LibInput::Connection::deviceAdded, this, resetModel); + connect(c, &LibInput::Connection::deviceRemoved, this, resetModel); +} + +InputDeviceModel::~InputDeviceModel() = default; + + +int InputDeviceModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return 2; +} + +QVariant InputDeviceModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + if (!index.parent().isValid() && index.column() == 0) { + const auto devices = LibInput::Connection::self()->devices(); + if (index.row() >= devices.count()) { + return QVariant(); + } + if (role == Qt::DisplayRole) { + return devices.at(index.row())->name(); + } + } + if (index.parent().isValid()) { + if (role == Qt::DisplayRole) { + const auto device = LibInput::Connection::self()->devices().at(index.parent().row()); + const auto property = device->metaObject()->property(index.row()); + if (index.column() == 0) { + return property.name(); + } else if (index.column() == 1) { + return device->property(property.name()); + } + } + } + return QVariant(); +} + +QModelIndex InputDeviceModel::index(int row, int column, const QModelIndex &parent) const +{ + if (column >= 2) { + return QModelIndex(); + } + if (parent.isValid()) { + if (parent.internalId() & s_propertyBitMask) { + return QModelIndex(); + } + if (row >= LibInput::Connection::self()->devices().at(parent.row())->metaObject()->propertyCount()) { + return QModelIndex(); + } + return createIndex(row, column, quint32(row + 1) << 16 | parent.internalId()); + } + if (row >= LibInput::Connection::self()->devices().count()) { + return QModelIndex(); + } + return createIndex(row, column, row + 1); +} + +int InputDeviceModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) { + return LibInput::Connection::self()->devices().count(); + } + if (parent.internalId() & s_propertyBitMask) { + return 0; + } + + return LibInput::Connection::self()->devices().at(parent.row())->metaObject()->propertyCount(); +} + +QModelIndex InputDeviceModel::parent(const QModelIndex &child) const +{ + if (child.internalId() & s_propertyBitMask) { + const quintptr parentId = child.internalId() & s_clientBitMask; + return createIndex(parentId - 1, 0, parentId); + } + return QModelIndex(); +} + +#endif + } diff --git a/debug_console.h b/debug_console.h index 596e6aedd..8309a463f 100644 --- a/debug_console.h +++ b/debug_console.h @@ -1,141 +1,158 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 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 . *********************************************************************/ #ifndef KWIN_DEBUG_CONSOLE_H #define KWIN_DEBUG_CONSOLE_H #include +#include #include "input.h" #include #include #include class QTextEdit; namespace Ui { class DebugConsole; } namespace KWin { class Client; class ShellClient; class Unmanaged; class DebugConsoleFilter; class KWIN_EXPORT DebugConsoleModel : public QAbstractItemModel { Q_OBJECT public: explicit DebugConsoleModel(QObject *parent = nullptr); virtual ~DebugConsoleModel(); int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QModelIndex index(int row, int column, const QModelIndex & parent) const override; int rowCount(const QModelIndex &parent) const override; QModelIndex parent(const QModelIndex &child) const override; private: template QModelIndex indexForClient(int row, int column, const QVector &clients, int id) const; template QModelIndex indexForProperty(int row, int column, const QModelIndex &parent, T *(DebugConsoleModel::*filter)(const QModelIndex&) const) const; template int propertyCount(const QModelIndex &parent, T *(DebugConsoleModel::*filter)(const QModelIndex&) const) const; QVariant propertyData(QObject *object, const QModelIndex &index, int role) const; template QVariant clientData(const QModelIndex &index, int role, const QVector clients) const; template void add(int parentRow, QVector &clients, T *client); template void remove(int parentRow, QVector &clients, T *client); ShellClient *shellClient(const QModelIndex &index) const; ShellClient *internalClient(const QModelIndex &index) const; Client *x11Client(const QModelIndex &index) const; Unmanaged *unmanaged(const QModelIndex &index) const; int topLevelRowCount() const; QVector m_shellClients; QVector m_internalClients; QVector m_x11Clients; QVector m_unmanageds; }; class DebugConsoleDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit DebugConsoleDelegate(QObject *parent = nullptr); virtual ~DebugConsoleDelegate(); QString displayText(const QVariant &value, const QLocale &locale) const override; }; class DebugConsole : public QWidget { Q_OBJECT public: DebugConsole(); virtual ~DebugConsole(); private: QScopedPointer m_ui; QScopedPointer m_inputFilter; }; class SurfaceTreeModel : public QAbstractItemModel { Q_OBJECT public: explicit SurfaceTreeModel(QObject *parent = nullptr); virtual ~SurfaceTreeModel(); int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QModelIndex index(int row, int column, const QModelIndex & parent) const override; int rowCount(const QModelIndex &parent) const override; QModelIndex parent(const QModelIndex &child) const override; }; class DebugConsoleFilter : public InputEventFilter { public: explicit DebugConsoleFilter(QTextEdit *textEdit); virtual ~DebugConsoleFilter(); bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override; bool wheelEvent(QWheelEvent *event) override; bool keyEvent(QKeyEvent *event) override; bool touchDown(quint32 id, const QPointF &pos, quint32 time) override; bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override; bool touchUp(quint32 id, quint32 time) override; private: QTextEdit *m_textEdit; }; +#if HAVE_INPUT +class InputDeviceModel : public QAbstractItemModel +{ + Q_OBJECT +public: + explicit InputDeviceModel(QObject *parent = nullptr); + virtual ~InputDeviceModel(); + + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + QModelIndex index(int row, int column, const QModelIndex & parent) const override; + int rowCount(const QModelIndex &parent) const override; + QModelIndex parent(const QModelIndex &child) const override; +}; +#endif + } #endif diff --git a/debug_console.ui b/debug_console.ui index d2a5ef793..08ea192bb 100644 --- a/debug_console.ui +++ b/debug_console.ui @@ -1,96 +1,106 @@ DebugConsole 0 0 600 600 Form Qt::Horizontal 40 20 Quit Debug Console 0 Windows 250 Surfaces Input Events false true + + + Input Devices + + + + + + + quitButton diff --git a/libinput/connection.cpp b/libinput/connection.cpp index 1026f1db7..f0cc3642e 100644 --- a/libinput/connection.cpp +++ b/libinput/connection.cpp @@ -1,321 +1,337 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 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 "connection.h" #include "context.h" +#include "device.h" #include "events.h" #include "../logind.h" #include "../udev.h" #include "libinput_logging.h" #include #include #include #include namespace KWin { namespace LibInput { Connection *Connection::s_self = nullptr; QThread *Connection::s_thread = nullptr; static Context *s_context = nullptr; Connection::Connection(QObject *parent) : Connection(nullptr, parent) { // only here to fix build, using will crash, BUG 343529 } Connection *Connection::create(QObject *parent) { Q_ASSERT(!s_self); static Udev s_udev; if (!s_udev.isValid()) { qCWarning(KWIN_LIBINPUT) << "Failed to initialize udev"; return nullptr; } if (!s_context) { s_context = new Context(s_udev); if (!s_context->isValid()) { qCWarning(KWIN_LIBINPUT) << "Failed to create context from udev"; delete s_context; s_context = nullptr; return nullptr; } // TODO: don't hardcode seat name if (!s_context->assignSeat("seat0")) { qCWarning(KWIN_LIBINPUT) << "Failed to assign seat seat0"; delete s_context; s_context = nullptr; return nullptr; } } s_thread = new QThread(); s_self = new Connection(s_context); s_self->moveToThread(s_thread); s_thread->start(); QObject::connect(s_thread, &QThread::finished, s_self, &QObject::deleteLater); QObject::connect(s_thread, &QThread::finished, s_thread, &QObject::deleteLater); QObject::connect(parent, &QObject::destroyed, s_thread, &QThread::quit); return s_self; } Connection::Connection(Context *input, QObject *parent) : QObject(parent) , m_input(input) , m_notifier(nullptr) , m_mutex(QMutex::Recursive) { Q_ASSERT(m_input); } Connection::~Connection() { s_self = nullptr; delete s_context; s_context = nullptr; } void Connection::setup() { QMetaObject::invokeMethod(this, "doSetup", Qt::QueuedConnection); } void Connection::doSetup() { Q_ASSERT(!m_notifier); m_notifier = new QSocketNotifier(m_input->fileDescriptor(), QSocketNotifier::Read, this); connect(m_notifier, &QSocketNotifier::activated, this, &Connection::handleEvent); LogindIntegration *logind = LogindIntegration::self(); connect(logind, &LogindIntegration::sessionActiveChanged, this, [this](bool active) { if (active) { if (!m_input->isSuspended()) { return; } m_input->resume(); wasSuspended = true; } else { deactivate(); } } ); handleEvent(); } void Connection::deactivate() { if (m_input->isSuspended()) { return; } m_keyboardBeforeSuspend = hasKeyboard(); m_pointerBeforeSuspend = hasPointer(); m_touchBeforeSuspend = hasTouch(); m_input->suspend(); handleEvent(); } void Connection::handleEvent() { QMutexLocker locker(&m_mutex); const bool wasEmpty = m_eventQueue.isEmpty(); do { m_input->dispatch(); Event *event = m_input->event(); if (!event) { break; } m_eventQueue << event; } while (true); if (wasEmpty && !m_eventQueue.isEmpty()) { emit eventsRead(); } } void Connection::processEvents() { QMutexLocker locker(&m_mutex); while (!m_eventQueue.isEmpty()) { QScopedPointer event(m_eventQueue.takeFirst()); switch (event->type()) { - case LIBINPUT_EVENT_DEVICE_ADDED: - if (libinput_device_has_capability(event->device(), LIBINPUT_DEVICE_CAP_KEYBOARD)) { + case LIBINPUT_EVENT_DEVICE_ADDED: { + auto device = new Device(event->device(), this); + m_devices << device; + if (device->isKeyboard()) { m_keyboard++; if (m_keyboard == 1) { emit hasKeyboardChanged(true); } } - if (libinput_device_has_capability(event->device(), LIBINPUT_DEVICE_CAP_POINTER)) { + if (device->isPointer()) { m_pointer++; if (m_pointer == 1) { emit hasPointerChanged(true); } } - if (libinput_device_has_capability(event->device(), LIBINPUT_DEVICE_CAP_TOUCH)) { + if (device->isTouch()) { m_touch++; if (m_touch == 1) { emit hasTouchChanged(true); } } + emit deviceAdded(device); break; - case LIBINPUT_EVENT_DEVICE_REMOVED: - if (libinput_device_has_capability(event->device(), LIBINPUT_DEVICE_CAP_KEYBOARD)) { + } + case LIBINPUT_EVENT_DEVICE_REMOVED: { + auto it = std::find_if(m_devices.begin(), m_devices.end(), [&event] (Device *d) { return event->device() == d->device(); } ); + if (it == m_devices.end()) { + // we don't know this device + break; + } + auto device = *it; + m_devices.erase(it); + emit deviceRemoved(device); + + if (device->isKeyboard()) { m_keyboard--; if (m_keyboard == 0) { emit hasKeyboardChanged(false); } } - if (libinput_device_has_capability(event->device(), LIBINPUT_DEVICE_CAP_POINTER)) { + if (device->isPointer()) { m_pointer--; if (m_pointer == 0) { emit hasPointerChanged(false); } } - if (libinput_device_has_capability(event->device(), LIBINPUT_DEVICE_CAP_TOUCH)) { + if (device->isTouch()) { m_touch--; if (m_touch == 0) { emit hasTouchChanged(false); } } + delete device; break; + } case LIBINPUT_EVENT_KEYBOARD_KEY: { KeyEvent *ke = static_cast(event.data()); emit keyChanged(ke->key(), ke->state(), ke->time()); break; } case LIBINPUT_EVENT_POINTER_AXIS: { PointerEvent *pe = static_cast(event.data()); struct Axis { qreal delta = 0.0; quint32 time = 0; }; QMap deltas; auto update = [&deltas] (PointerEvent *pe) { const auto axis = pe->axis(); for (auto it = axis.begin(); it != axis.end(); ++it) { deltas[*it].delta += pe->axisValue(*it); deltas[*it].time = pe->time(); } }; update(pe); auto it = m_eventQueue.begin(); while (it != m_eventQueue.end()) { if ((*it)->type() == LIBINPUT_EVENT_POINTER_AXIS) { QScopedPointer p(static_cast(*it)); update(p.data()); it = m_eventQueue.erase(it); } else { break; } } for (auto it = deltas.constBegin(); it != deltas.constEnd(); ++it) { emit pointerAxisChanged(it.key(), it.value().delta, it.value().time); } break; } case LIBINPUT_EVENT_POINTER_BUTTON: { PointerEvent *pe = static_cast(event.data()); emit pointerButtonChanged(pe->button(), pe->buttonState(), pe->time()); break; } case LIBINPUT_EVENT_POINTER_MOTION: { PointerEvent *pe = static_cast(event.data()); QPointF delta = pe->delta(); quint32 latestTime = pe->time(); auto it = m_eventQueue.begin(); while (it != m_eventQueue.end()) { if ((*it)->type() == LIBINPUT_EVENT_POINTER_MOTION) { QScopedPointer p(static_cast(*it)); delta += p->delta(); latestTime = p->time(); it = m_eventQueue.erase(it); } else { break; } } emit pointerMotion(delta, latestTime); break; } case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: { PointerEvent *pe = static_cast(event.data()); emit pointerMotionAbsolute(pe->absolutePos(), pe->absolutePos(m_size), pe->time()); break; } case LIBINPUT_EVENT_TOUCH_DOWN: { TouchEvent *te = static_cast(event.data()); emit touchDown(te->id(), te->absolutePos(m_size), te->time()); break; } case LIBINPUT_EVENT_TOUCH_UP: { TouchEvent *te = static_cast(event.data()); emit touchUp(te->id(), te->time()); break; } case LIBINPUT_EVENT_TOUCH_MOTION: { TouchEvent *te = static_cast(event.data()); emit touchMotion(te->id(), te->absolutePos(m_size), te->time()); break; } case LIBINPUT_EVENT_TOUCH_CANCEL: { emit touchCanceled(); break; } case LIBINPUT_EVENT_TOUCH_FRAME: { emit touchFrame(); break; } default: // nothing break; } } if (wasSuspended) { if (m_keyboardBeforeSuspend && !m_keyboard) { emit hasKeyboardChanged(false); } if (m_pointerBeforeSuspend && !m_pointer) { emit hasPointerChanged(false); } if (m_touchBeforeSuspend && !m_touch) { emit hasTouchChanged(false); } wasSuspended = false; } } void Connection::setScreenSize(const QSize &size) { m_size = size; } bool Connection::isSuspended() const { if (!s_context) { return false; } return s_context->isSuspended(); } } } diff --git a/libinput/connection.h b/libinput/connection.h index 65ed4f52a..b9133aba8 100644 --- a/libinput/connection.h +++ b/libinput/connection.h @@ -1,114 +1,122 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 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 . *********************************************************************/ #ifndef KWIN_LIBINPUT_CONNECTION_H #define KWIN_LIBINPUT_CONNECTION_H #include "../input.h" #include #include #include #include #include class QSocketNotifier; class QThread; namespace KWin { namespace LibInput { class Event; +class Device; class Context; class Connection : public QObject { Q_OBJECT public: ~Connection(); void setup(); /** * Sets the screen @p size. This is needed for mapping absolute pointer events to * the screen data. **/ void setScreenSize(const QSize &size); bool hasKeyboard() const { return m_keyboard > 0; } bool hasTouch() const { return m_touch > 0; } bool hasPointer() const { return m_pointer > 0; } bool isSuspended() const; void deactivate(); void processEvents(); + QVector devices() const { + return m_devices; + } + Q_SIGNALS: void keyChanged(quint32 key, KWin::InputRedirection::KeyboardKeyState, quint32 time); void pointerButtonChanged(quint32 button, KWin::InputRedirection::PointerButtonState state, quint32 time); void pointerMotionAbsolute(QPointF orig, QPointF screen, quint32 time); void pointerMotion(QPointF delta, quint32 time); void pointerAxisChanged(KWin::InputRedirection::PointerAxis axis, qreal delta, quint32 time); void touchFrame(); void touchCanceled(); void touchDown(qint32 id, const QPointF &absolutePos, quint32 time); void touchUp(qint32 id, quint32 time); void touchMotion(qint32 id, const QPointF &absolutePos, quint32 time); void hasKeyboardChanged(bool); void hasPointerChanged(bool); void hasTouchChanged(bool); + void deviceAdded(KWin::LibInput::Device *); + void deviceRemoved(KWin::LibInput::Device *); void eventsRead(); private Q_SLOTS: void doSetup(); private: Connection(Context *input, QObject *parent = nullptr); void handleEvent(); Context *m_input; QSocketNotifier *m_notifier; QSize m_size; int m_keyboard = 0; int m_pointer = 0; int m_touch = 0; bool m_keyboardBeforeSuspend = false; bool m_pointerBeforeSuspend = false; bool m_touchBeforeSuspend = false; QMutex m_mutex; QVector m_eventQueue; bool wasSuspended = false; + QVector m_devices; KWIN_SINGLETON(Connection) static QThread *s_thread; }; } } #endif diff --git a/libinput/device.cpp b/libinput/device.cpp new file mode 100644 index 000000000..26e4f860a --- /dev/null +++ b/libinput/device.cpp @@ -0,0 +1,127 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2016 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 "device.h" +#include + +#include + +namespace KWin +{ +namespace LibInput +{ + +static bool checkAlphaNumericKeyboard(libinput_device *device) +{ + for (uint i = KEY_1; i <= KEY_0; i++) { + if (libinput_device_keyboard_has_key(device, i) == 0) { + return false; + } + } + for (uint i = KEY_Q; i <= KEY_P; i++) { + if (libinput_device_keyboard_has_key(device, i) == 0) { + return false; + } + } + for (uint i = KEY_A; i <= KEY_L; i++) { + if (libinput_device_keyboard_has_key(device, i) == 0) { + return false; + } + } + for (uint i = KEY_Z; i <= KEY_M; i++) { + if (libinput_device_keyboard_has_key(device, i) == 0) { + return false; + } + } + return true; +} + +Device::Device(libinput_device *device, QObject *parent) + : QObject(parent) + , m_device(device) + , m_keyboard(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_KEYBOARD)) + , m_pointer(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_POINTER)) + , m_touch(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_TOUCH)) + , m_tabletTool(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_TABLET_TOOL)) +#if 0 + // next libinput version + , m_tabletPad(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_TABLET_PAD)) +#else + , m_tabletPad(false) +#endif + , m_supportsGesture(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_GESTURE)) + , m_name(QString::fromLocal8Bit(libinput_device_get_name(m_device))) + , m_sysName(QString::fromLocal8Bit(libinput_device_get_sysname(m_device))) + , m_outputName(QString::fromLocal8Bit(libinput_device_get_output_name(m_device))) + , m_product(libinput_device_get_id_product(m_device)) + , m_vendor(libinput_device_get_id_vendor(m_device)) + , m_tapFingerCount(libinput_device_config_tap_get_finger_count(m_device)) + , m_tapEnabledByDefault(libinput_device_config_tap_get_default_enabled(m_device) == LIBINPUT_CONFIG_TAP_ENABLED) + , m_supportsDisableWhileTyping(libinput_device_config_dwt_is_available(m_device)) + , m_supportsPointerAcceleration(libinput_device_config_accel_is_available(m_device)) + , m_supportsLeftHanded(libinput_device_config_left_handed_is_available(m_device)) + , m_supportsCalibrationMatrix(libinput_device_config_calibration_has_matrix(m_device)) + , m_supportsDisableEvents(libinput_device_config_send_events_get_modes(m_device) & LIBINPUT_CONFIG_SEND_EVENTS_DISABLED) + , m_supportsDisableEventsOnExternalMouse(libinput_device_config_send_events_get_modes(m_device) & LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE) +{ + libinput_device_ref(m_device); + + qreal width = 0; + qreal height = 0; + if (libinput_device_get_size(m_device, &width, &height) == 0) { + m_size = QSizeF(width, height); + } + if (m_pointer) { + if (libinput_device_pointer_has_button(m_device, BTN_LEFT) == 1) { + m_supportedButtons |= Qt::LeftButton; + } + if (libinput_device_pointer_has_button(m_device, BTN_MIDDLE) == 1) { + m_supportedButtons |= Qt::MiddleButton; + } + if (libinput_device_pointer_has_button(m_device, BTN_RIGHT) == 1) { + m_supportedButtons |= Qt::RightButton; + } + if (libinput_device_pointer_has_button(m_device, BTN_SIDE) == 1) { + m_supportedButtons |= Qt::ExtraButton1; + } + if (libinput_device_pointer_has_button(m_device, BTN_EXTRA) == 1) { + m_supportedButtons |= Qt::ExtraButton2; + } + if (libinput_device_pointer_has_button(m_device, BTN_BACK) == 1) { + m_supportedButtons |= Qt::BackButton; + } + if (libinput_device_pointer_has_button(m_device, BTN_FORWARD) == 1) { + m_supportedButtons |= Qt::ForwardButton; + } + if (libinput_device_pointer_has_button(m_device, BTN_TASK) == 1) { + m_supportedButtons |= Qt::TaskButton; + } + } + if (m_keyboard) { + m_alphaNumericKeyboard = checkAlphaNumericKeyboard(m_device); + } +} + +Device::~Device() +{ + libinput_device_unref(m_device); +} + +} +} diff --git a/libinput/device.h b/libinput/device.h new file mode 100644 index 000000000..d9a80e433 --- /dev/null +++ b/libinput/device.h @@ -0,0 +1,164 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2016 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 . +*********************************************************************/ +#ifndef KWIN_LIBINPUT_DEVICE_H +#define KWIN_LIBINPUT_DEVICE_H + +#include +#include + +struct libinput_device; + +namespace KWin +{ +namespace LibInput +{ + +class Device : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool keyboard READ isKeyboard CONSTANT) + Q_PROPERTY(bool alphaNumericKeyboard READ isAlphaNumericKeyboard CONSTANT) + Q_PROPERTY(bool pointer READ isPointer CONSTANT) + Q_PROPERTY(bool touch READ isTouch CONSTANT) + Q_PROPERTY(bool tabletTool READ isTabletTool CONSTANT) + Q_PROPERTY(bool tabletPad READ isTabletPad CONSTANT) + Q_PROPERTY(bool gestureSupport READ supportsGesture CONSTANT) + Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(QString sysName READ sysName CONSTANT) + Q_PROPERTY(QString outputName READ outputName CONSTANT) + Q_PROPERTY(QSizeF size READ size CONSTANT) + Q_PROPERTY(quint32 product READ product CONSTANT) + Q_PROPERTY(quint32 vendor READ vendor CONSTANT) + Q_PROPERTY(Qt::MouseButtons supportedButtons READ supportedButtons CONSTANT) + Q_PROPERTY(int tapFingerCount READ tapFingerCount CONSTANT) + Q_PROPERTY(bool tapEnabledByDefault READ tapEnabledByDefault CONSTANT) + Q_PROPERTY(bool supportsDisableWhileTyping READ supportsDisableWhileTyping CONSTANT) + Q_PROPERTY(bool supportsPointerAcceleration READ supportsPointerAcceleration CONSTANT) + Q_PROPERTY(bool supportsLeftHanded READ supportsLeftHanded CONSTANT) + Q_PROPERTY(bool supportsCalibrationMatrix READ supportsCalibrationMatrix CONSTANT) + Q_PROPERTY(bool supportsDisableEvents READ supportsDisableEvents CONSTANT) + Q_PROPERTY(bool supportsDisableEventsOnExternalMouse READ supportsDisableEventsOnExternalMouse CONSTANT) +public: + explicit Device(libinput_device *device, QObject *parent = nullptr); + virtual ~Device(); + + bool isKeyboard() const { + return m_keyboard; + } + bool isAlphaNumericKeyboard() const { + return m_alphaNumericKeyboard; + } + bool isPointer() const { + return m_pointer; + } + bool isTouch() const { + return m_touch; + } + bool isTabletTool() const { + return m_tabletTool; + } + bool isTabletPad() const { + return m_tabletPad; + } + bool supportsGesture() const { + return m_supportsGesture; + } + QString name() const { + return m_name; + } + QString sysName() const { + return m_sysName; + } + QString outputName() const { + return m_outputName; + } + QSizeF size() const { + return m_size; + } + quint32 product() const { + return m_product; + } + quint32 vendor() const { + return m_vendor; + } + Qt::MouseButtons supportedButtons() const { + return m_supportedButtons; + } + int tapFingerCount() const { + return m_tapFingerCount; + } + bool tapEnabledByDefault() const { + return m_tapEnabledByDefault; + } + bool supportsDisableWhileTyping() const { + return m_supportsDisableWhileTyping; + } + bool supportsPointerAcceleration() const { + return m_supportsPointerAcceleration; + } + bool supportsLeftHanded() const { + return m_supportsLeftHanded; + } + bool supportsCalibrationMatrix() const { + return m_supportsCalibrationMatrix; + } + bool supportsDisableEvents() const { + return m_supportsDisableEvents; + } + bool supportsDisableEventsOnExternalMouse() const { + return m_supportsDisableEventsOnExternalMouse; + } + + libinput_device *device() const { + return m_device; + } + +private: + libinput_device *m_device; + bool m_keyboard; + bool m_alphaNumericKeyboard = false; + bool m_pointer; + bool m_touch; + bool m_tabletTool; + bool m_tabletPad; + bool m_supportsGesture; + QString m_name; + QString m_sysName; + QString m_outputName; + QSizeF m_size; + quint32 m_product; + quint32 m_vendor; + Qt::MouseButtons m_supportedButtons = Qt::NoButton; + int m_tapFingerCount; + bool m_tapEnabledByDefault; + bool m_supportsDisableWhileTyping; + bool m_supportsPointerAcceleration; + bool m_supportsLeftHanded; + bool m_supportsCalibrationMatrix; + bool m_supportsDisableEvents; + bool m_supportsDisableEventsOnExternalMouse; +}; + +} +} + +Q_DECLARE_METATYPE(KWin::LibInput::Device*) + +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 31afdbd6b..e5f199937 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,33 +1,34 @@ if (XCB_ICCCM_FOUND) set(normalhintsbasesizetest_SRCS normalhintsbasesizetest.cpp) add_executable(normalhintsbasesizetest ${normalhintsbasesizetest_SRCS}) target_link_libraries(normalhintsbasesizetest XCB::XCB XCB::ICCCM KF5::WindowSystem) endif() # next target set(screenedgeshowtest_SRCS screenedgeshowtest.cpp) add_executable(screenedgeshowtest ${screenedgeshowtest_SRCS}) target_link_libraries(screenedgeshowtest Qt5::Widgets Qt5::X11Extras KF5::ConfigCore KF5::WindowSystem ${XCB_XCB_LIBRARY}) if (KF5Wayland_FOUND) add_definitions(-DSOURCE_DIR="${KWIN_SOURCE_DIR}") set(waylandclienttest_SRCS waylandclienttest.cpp ) add_executable(waylandclienttest ${waylandclienttest_SRCS}) target_link_libraries(waylandclienttest Qt5::Core Qt5::Gui KF5::WaylandClient) endif() if (HAVE_INPUT) set(libinputtest_SRCS libinputtest.cpp ${KWIN_SOURCE_DIR}/libinput/context.cpp ${KWIN_SOURCE_DIR}/libinput/connection.cpp + ${KWIN_SOURCE_DIR}/libinput/device.cpp ${KWIN_SOURCE_DIR}/libinput/events.cpp ${KWIN_SOURCE_DIR}/libinput/libinput_logging.cpp ${KWIN_SOURCE_DIR}/logind.cpp ${KWIN_SOURCE_DIR}/udev.cpp ) add_executable(libinputtest ${libinputtest_SRCS}) target_link_libraries(libinputtest Qt5::Core Qt5::DBus Libinput::Libinput ${UDEV_LIBS} KF5::WindowSystem) endif()