diff --git a/CMakeLists.txt b/CMakeLists.txt index d70dbc50b..a99928d8b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,723 +1,742 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) project(KWIN) set(PROJECT_VERSION "5.15.80") set(PROJECT_VERSION_MAJOR 5) set(QT_MIN_VERSION "5.11.0") set(KF5_MIN_VERSION "5.56.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 Sensors 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 -DQT_NO_URL_CAST_FROM_STRING) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) # This is a workaround/compromise for a Kwin specific policy of not applying the relevant override fix. # See thread in D18167. if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-inconsistent-missing-override") else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-suggest-override") 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 Wayland ) # required frameworks by config modules find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Completion Declarative KCMUtils KIO TextWidgets 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(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.13.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 "http://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 "http://www.freedesktop.org/software/systemd/libudev/" 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() 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 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(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 "http://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 "http://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}) 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") 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) 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) configure_file(config-kwin.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kwin.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") ########### 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_KDEINIT_SRCS workspace.cpp dbusinterface.cpp virtualdesktopsdbustypes.cpp abstract_client.cpp client.cpp client_machine.cpp cursor.cpp debug_console.cpp tabgroup.cpp focuschain.cpp globalshortcuts.cpp input.cpp input_event.cpp input_event_spy.cpp keyboard_input.cpp keyboard_layout.cpp keyboard_layout_switching.cpp keyboard_repeat.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 outputscreens.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 screenlockerwatcher.cpp thumbnailitem.cpp deleted.cpp effects.cpp effectloader.cpp virtualdesktops.cpp xcbutils.cpp x11eventfilter.cpp logind.cpp onscreennotification.cpp osd.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 platform.cpp abstract_output.cpp shell_client.cpp wayland_server.cpp wayland_cursor_theme.cpp virtualkeyboard.cpp virtualkeyboard_dbus.cpp appmenu.cpp modifier_only_shortcuts.cpp xkb.cpp gestures.cpp popup_input_filter.cpp colorcorrection/manager.cpp colorcorrection/colorcorrectdbusinterface.cpp colorcorrection/suncalc.cpp abstract_opengl_context_attribute_builder.cpp egl_context_attribute_builder.cpp was_user_interaction_x11_filter.cpp moving_client_x11_filter.cpp window_property_notify_x11_filter.cpp rootinfo_filter.cpp orientation_sensor.cpp idle_inhibition.cpp libinput/context.cpp libinput/connection.cpp libinput/device.cpp libinput/events.cpp libinput/libinput_logging.cpp udev.cpp touch_hide_cursor_spy.cpp ) include(ECMQtDeclareLoggingCategory) ecm_qt_declare_logging_category(kwin_KDEINIT_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_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 tabbox/x11_filter.cpp ) endif() if(KWIN_BUILD_ACTIVITIES) set( kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} activities.cpp ) endif() if (HAVE_LINUX_VT_H) set(kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} virtual_terminal.cpp ) endif() kconfig_add_kcfg_files(kwin_KDEINIT_SRCS settings.kcfgc) kconfig_add_kcfg_files(kwin_KDEINIT_SRCS colorcorrection/colorcorrect_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 org.kde.kwin.ColorCorrect.xml colorcorrection/colorcorrectdbusinterface.h KWin::ColorCorrect::ColorCorrectDBusInterface ) qt5_add_dbus_adaptor( kwin_KDEINIT_SRCS ${kwin_effects_dbus_xml} effects.h KWin::EffectsHandlerImpl ) qt5_add_dbus_adaptor( kwin_KDEINIT_SRCS org.kde.kwin.OrientationSensor.xml orientation_sensor.h KWin::OrientationSensor) qt5_add_dbus_adaptor( kwin_KDEINIT_SRCS org.kde.KWin.VirtualDesktopManager.xml dbusinterface.h KWin::VirtualDesktopManagerDBusInterface ) qt5_add_dbus_interface( kwin_KDEINIT_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/org.freedesktop.ScreenSaver.xml screenlocker_interface) qt5_add_dbus_interface( kwin_KDEINIT_SRCS org.kde.kappmenu.xml appmenu_interface ) ki18n_wrap_ui(kwin_KDEINIT_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::Sensors Qt5::Script ) set(kwin_KDE_LIBS KF5::ConfigCore KF5::CoreAddons KF5::ConfigWidgets KF5::GlobalAccel KF5::GlobalAccelPrivate KF5::I18n KF5::Notifications KF5::Package KF5::Plasma KF5::WindowSystem KF5::QuickAddons KDecoration2::KDecoration KDecoration2::KDecoration2Private PW::KScreenLocker ) set(kwin_XLIB_LIBS ${X11_X11_LIB} ${X11_ICE_LIB} ${X11_SM_LIB} ) 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::ICCCM ) 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} ${UDEV_LIBS} Libinput::Libinput ) 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}) kf5_add_kdeinit_executable(kwin_x11 main_x11.cpp) target_link_libraries(kdeinit_kwin_x11 kwin KF5::Crash Qt5::X11Extras) 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} ) +set(kwin_XWAYLAND_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/xwl/xwayland.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/xwl/databridge.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/clipboard.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 tabletmodemanager.cpp main_wayland.cpp - xwl/xwayland.cpp ) -add_executable(kwin_wayland ${kwin_WAYLAND_SRCS}) +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.xml org.kde.kwin.Compositing.xml org.kde.kwin.ColorCorrect.xml org.kde.kwin.Effects.xml org.kde.KWin.VirtualDesktopManager.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) add_subdirectory(packageplugins) if (BUILD_TESTING) add_subdirectory(autotests) add_subdirectory(tests) endif() if (KF5DocTools_FOUND) add_subdirectory(doc) endif() add_subdirectory(kconf_update) 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/atoms.cpp b/atoms.cpp index ee9e8e29e..159c8f9b4 100644 --- a/atoms.cpp +++ b/atoms.cpp @@ -1,84 +1,92 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak Copyright (C) 2013 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 "atoms.h" namespace KWin { Atoms::Atoms() : kwin_running(QByteArrayLiteral("KWIN_RUNNING")) , activities(QByteArrayLiteral("_KDE_NET_WM_ACTIVITIES")) , wm_protocols(QByteArrayLiteral("WM_PROTOCOLS")) , wm_delete_window(QByteArrayLiteral("WM_DELETE_WINDOW")) , wm_take_focus(QByteArrayLiteral("WM_TAKE_FOCUS")) , wm_change_state(QByteArrayLiteral("WM_CHANGE_STATE")) , wm_client_leader(QByteArrayLiteral("WM_CLIENT_LEADER")) , wm_window_role(QByteArrayLiteral("WM_WINDOW_ROLE")) , wm_state(QByteArrayLiteral("WM_STATE")) , sm_client_id(QByteArrayLiteral("SM_CLIENT_ID")) , motif_wm_hints(QByteArrayLiteral("_MOTIF_WM_HINTS")) , net_wm_context_help(QByteArrayLiteral("_NET_WM_CONTEXT_HELP")) , net_wm_ping(QByteArrayLiteral("_NET_WM_PING")) , net_wm_user_time(QByteArrayLiteral("_NET_WM_USER_TIME")) , kde_net_wm_user_creation_time(QByteArrayLiteral("_KDE_NET_WM_USER_CREATION_TIME")) , net_wm_take_activity(QByteArrayLiteral("_NET_WM_TAKE_ACTIVITY")) , net_wm_window_opacity(QByteArrayLiteral("_NET_WM_WINDOW_OPACITY")) , xdnd_aware(QByteArrayLiteral("XdndAware")) , xdnd_position(QByteArrayLiteral("XdndPosition")) , net_frame_extents(QByteArrayLiteral("_NET_FRAME_EXTENTS")) , kde_net_wm_frame_strut(QByteArrayLiteral("_KDE_NET_WM_FRAME_STRUT")) , net_wm_sync_request_counter(QByteArrayLiteral("_NET_WM_SYNC_REQUEST_COUNTER")) , net_wm_sync_request(QByteArrayLiteral("_NET_WM_SYNC_REQUEST")) , kde_net_wm_shadow(QByteArrayLiteral("_KDE_NET_WM_SHADOW")) , kde_net_wm_tab_group(QByteArrayLiteral("_KDE_NET_WM_TAB_GROUP")) , kde_first_in_window_list(QByteArrayLiteral("_KDE_FIRST_IN_WINDOWLIST")) , kde_color_sheme(QByteArrayLiteral("_KDE_NET_WM_COLOR_SCHEME")) , kde_skip_close_animation(QByteArrayLiteral("_KDE_NET_WM_SKIP_CLOSE_ANIMATION")) , kde_screen_edge_show(QByteArrayLiteral("_KDE_NET_WM_SCREEN_EDGE_SHOW")) , gtk_frame_extents(QByteArrayLiteral("_GTK_FRAME_EXTENTS")) , kwin_dbus_service(QByteArrayLiteral("_ORG_KDE_KWIN_DBUS_SERVICE")) , utf8_string(QByteArrayLiteral("UTF8_STRING")) + , text(QByteArrayLiteral("TEXT")) + , uri_list(QByteArrayLiteral("text/uri-list")) , wl_surface_id(QByteArrayLiteral("WL_SURFACE_ID")) , kde_net_wm_appmenu_service_name(QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME")) , kde_net_wm_appmenu_object_path(QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH")) + , clipboard(QByteArrayLiteral("CLIPBOARD")) + , timestamp(QByteArrayLiteral("TIMESTAMP")) + , targets(QByteArrayLiteral("TARGETS")) + , delete_atom(QByteArrayLiteral("DELETE")) + , incr(QByteArrayLiteral("INCR")) + , wl_selection(QByteArrayLiteral("WL_SELECTION")) , m_dtSmWindowInfo(QByteArrayLiteral("_DT_SM_WINDOW_INFO")) , m_motifSupport(QByteArrayLiteral("_MOTIF_WM_INFO")) , m_helpersRetrieved(false) { } void Atoms::retrieveHelpers() { if (m_helpersRetrieved) { return; } // just retrieve the atoms once, all others are retrieved when being accessed // Q_UNUSED is used in the hope that the compiler doesn't optimize the operations away xcb_atom_t atom = m_dtSmWindowInfo; Q_UNUSED(atom) atom = m_motifSupport; Q_UNUSED(atom) m_helpersRetrieved = true; } } // namespace diff --git a/atoms.h b/atoms.h index 2c7b2d9f1..e957998e4 100644 --- a/atoms.h +++ b/atoms.h @@ -1,91 +1,99 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak Copyright (C) 2013 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_ATOMS_H #define KWIN_ATOMS_H #include "xcbutils.h" namespace KWin { class KWIN_EXPORT Atoms { public: Atoms(); Xcb::Atom kwin_running; Xcb::Atom activities; Xcb::Atom wm_protocols; Xcb::Atom wm_delete_window; Xcb::Atom wm_take_focus; Xcb::Atom wm_change_state; Xcb::Atom wm_client_leader; Xcb::Atom wm_window_role; Xcb::Atom wm_state; Xcb::Atom sm_client_id; Xcb::Atom motif_wm_hints; Xcb::Atom net_wm_context_help; Xcb::Atom net_wm_ping; Xcb::Atom net_wm_user_time; Xcb::Atom kde_net_wm_user_creation_time; Xcb::Atom net_wm_take_activity; Xcb::Atom net_wm_window_opacity; Xcb::Atom xdnd_aware; Xcb::Atom xdnd_position; Xcb::Atom net_frame_extents; Xcb::Atom kde_net_wm_frame_strut; Xcb::Atom net_wm_sync_request_counter; Xcb::Atom net_wm_sync_request; Xcb::Atom kde_net_wm_shadow; Xcb::Atom kde_net_wm_tab_group; Xcb::Atom kde_first_in_window_list; Xcb::Atom kde_color_sheme; Xcb::Atom kde_skip_close_animation; Xcb::Atom kde_screen_edge_show; Xcb::Atom gtk_frame_extents; Xcb::Atom kwin_dbus_service; Xcb::Atom utf8_string; + Xcb::Atom text; + Xcb::Atom uri_list; Xcb::Atom wl_surface_id; Xcb::Atom kde_net_wm_appmenu_service_name; Xcb::Atom kde_net_wm_appmenu_object_path; + Xcb::Atom clipboard; + Xcb::Atom timestamp; + Xcb::Atom targets; + Xcb::Atom delete_atom; + Xcb::Atom incr; + Xcb::Atom wl_selection; /** * @internal **/ void retrieveHelpers(); private: // helper atoms we need to resolve to "announce" support (see #172028) Xcb::Atom m_dtSmWindowInfo; Xcb::Atom m_motifSupport; bool m_helpersRetrieved; }; extern KWIN_EXPORT Atoms* atoms; } // namespace #endif diff --git a/autotests/integration/CMakeLists.txt b/autotests/integration/CMakeLists.txt index b55b9f2c0..07528be27 100644 --- a/autotests/integration/CMakeLists.txt +++ b/autotests/integration/CMakeLists.txt @@ -1,88 +1,88 @@ add_subdirectory(helper) -add_library(KWinIntegrationTestFramework STATIC ../../xwl/xwayland.cpp kwin_wayland_test.cpp test_helpers.cpp) +add_library(KWinIntegrationTestFramework STATIC kwin_wayland_test.cpp test_helpers.cpp ${kwin_XWAYLAND_SRCS}) target_link_libraries(KWinIntegrationTestFramework kwin Qt5::Test) function(integrationTest) set(optionArgs WAYLAND_ONLY) set(oneValueArgs NAME) set(multiValueArgs SRCS LIBS) cmake_parse_arguments(ARGS "${optionArgs}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) add_executable(${ARGS_NAME} ${ARGS_SRCS}) target_link_libraries(${ARGS_NAME} KWinIntegrationTestFramework kwin Qt5::Test ${ARGS_LIBS}) add_test(NAME kwin-${ARGS_NAME} COMMAND dbus-run-session ${CMAKE_BINARY_DIR}/bin/${ARGS_NAME}) if (${ARGS_WAYLAND_ONLY}) add_executable(${ARGS_NAME}_waylandonly ${ARGS_SRCS}) set_target_properties(${ARGS_NAME}_waylandonly PROPERTIES COMPILE_DEFINITIONS "NO_XWAYLAND") target_link_libraries(${ARGS_NAME}_waylandonly KWinIntegrationTestFramework kwin Qt5::Test ${ARGS_LIBS}) add_test(NAME kwin-${ARGS_NAME}-waylandonly COMMAND dbus-run-session ${CMAKE_BINARY_DIR}/bin/${ARGS_NAME}_waylandonly) endif() endfunction() integrationTest(WAYLAND_ONLY NAME testStart SRCS start_test.cpp) integrationTest(WAYLAND_ONLY NAME testTransientNoInput SRCS transient_no_input_test.cpp) integrationTest(NAME testDontCrashGlxgears SRCS dont_crash_glxgears.cpp) integrationTest(NAME testLockScreen SRCS lockscreen.cpp) integrationTest(WAYLAND_ONLY NAME testDecorationInput SRCS decoration_input_test.cpp) integrationTest(WAYLAND_ONLY NAME testInternalWindow SRCS internal_window.cpp) integrationTest(WAYLAND_ONLY NAME testTouchInput SRCS touch_input_test.cpp) integrationTest(WAYLAND_ONLY NAME testInputStackingOrder SRCS input_stacking_order.cpp) integrationTest(NAME testPointerInput SRCS pointer_input.cpp) integrationTest(NAME testPlatformCursor SRCS platformcursor.cpp) integrationTest(WAYLAND_ONLY NAME testDontCrashCancelAnimation SRCS dont_crash_cancel_animation.cpp) integrationTest(WAYLAND_ONLY NAME testTransientPlacement SRCS transient_placement.cpp) integrationTest(NAME testDebugConsole SRCS debug_console_test.cpp) integrationTest(NAME testDontCrashEmptyDeco SRCS dont_crash_empty_deco.cpp) integrationTest(WAYLAND_ONLY NAME testPlasmaSurface SRCS plasma_surface_test.cpp) integrationTest(WAYLAND_ONLY NAME testMaximized SRCS maximize_test.cpp) integrationTest(WAYLAND_ONLY NAME testShellClient SRCS shell_client_test.cpp) integrationTest(WAYLAND_ONLY NAME testDontCrashNoBorder SRCS dont_crash_no_border.cpp) integrationTest(NAME testXClipboardSync SRCS xclipboardsync_test.cpp) integrationTest(WAYLAND_ONLY NAME testSceneOpenGL SRCS scene_opengl_test.cpp generic_scene_opengl_test.cpp) integrationTest(WAYLAND_ONLY NAME testSceneOpenGLShadow SRCS scene_opengl_shadow_test.cpp) integrationTest(WAYLAND_ONLY NAME testSceneOpenGLES SRCS scene_opengl_es_test.cpp generic_scene_opengl_test.cpp) integrationTest(WAYLAND_ONLY NAME testNoXdgRuntimeDir SRCS no_xdg_runtime_dir_test.cpp) integrationTest(WAYLAND_ONLY NAME testScreenChanges SRCS screen_changes_test.cpp) integrationTest(NAME testModiferOnlyShortcut SRCS modifier_only_shortcut_test.cpp) integrationTest(WAYLAND_ONLY NAME testTabBox SRCS tabbox_test.cpp) integrationTest(WAYLAND_ONLY NAME testWindowSelection SRCS window_selection_test.cpp) integrationTest(WAYLAND_ONLY NAME testPointerConstraints SRCS pointer_constraints_test.cpp) integrationTest(WAYLAND_ONLY NAME testKeyboardLayout SRCS keyboard_layout_test.cpp) integrationTest(WAYLAND_ONLY NAME testKeymapCreationFailure SRCS keymap_creation_failure_test.cpp) integrationTest(WAYLAND_ONLY NAME testShowingDesktop SRCS showing_desktop_test.cpp) integrationTest(WAYLAND_ONLY NAME testDontCrashUseractionsMenu SRCS dont_crash_useractions_menu.cpp) integrationTest(WAYLAND_ONLY NAME testKWinBindings SRCS kwinbindings_test.cpp) integrationTest(WAYLAND_ONLY NAME testVirtualDesktop SRCS virtual_desktop_test.cpp) integrationTest(WAYLAND_ONLY NAME testShellClientRules SRCS shell_client_rules_test.cpp) integrationTest(WAYLAND_ONLY NAME testIdleInhibition SRCS idle_inhibition_test.cpp) integrationTest(WAYLAND_ONLY NAME testColorCorrectNightColor SRCS colorcorrect_nightcolor_test.cpp) integrationTest(WAYLAND_ONLY NAME testDontCrashCursorPhysicalSizeEmpty SRCS dont_crash_cursor_physical_size_empty.cpp) integrationTest(WAYLAND_ONLY NAME testDontCrashReinitializeCompositor SRCS dont_crash_reinitialize_compositor.cpp) integrationTest(WAYLAND_ONLY NAME testNoGlobalShortcuts SRCS no_global_shortcuts_test.cpp) if (XCB_ICCCM_FOUND) integrationTest(NAME testMoveResize SRCS move_resize_window_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testStruts SRCS struts_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testShade SRCS shade_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testDontCrashAuroraeDestroyDeco SRCS dont_crash_aurorae_destroy_deco.cpp LIBS XCB::ICCCM) integrationTest(NAME testPlasmaWindow SRCS plasmawindow_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testScreenEdgeClientShow SRCS screenedge_client_show_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testX11DesktopWindow SRCS desktop_window_x11_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testXwaylandInput SRCS xwayland_input_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testWindowRules SRCS window_rules_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testX11Client SRCS x11_client_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testQuickTiling SRCS quick_tiling_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testGlobalShortcuts SRCS globalshortcuts_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testSceneQPainter SRCS scene_qpainter_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testSceneQPainterShadow SRCS scene_qpainter_shadow_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testStackingOrder SRCS stacking_order_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testDbusInterface SRCS dbus_interface_test.cpp LIBS XCB::ICCCM) if (KWIN_BUILD_ACTIVITIES) integrationTest(NAME testActivities SRCS activities_test.cpp LIBS XCB::ICCCM) endif() endif() add_subdirectory(scripting) add_subdirectory(effects) add_subdirectory(fakes) diff --git a/autotests/integration/kwin_wayland_test.cpp b/autotests/integration/kwin_wayland_test.cpp index 3cd360300..710f50842 100644 --- a/autotests/integration/kwin_wayland_test.cpp +++ b/autotests/integration/kwin_wayland_test.cpp @@ -1,155 +1,159 @@ /******************************************************************** 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 "kwin_wayland_test.h" #include "../../platform.h" #include "../../composite.h" #include "../../effects.h" #include "../../wayland_server.h" #include "../../workspace.h" #include "../../xcbutils.h" #include "../../xwl/xwayland.h" #include #include #include #include #include #include #include // system #include #include #include namespace KWin { WaylandTestApplication::WaylandTestApplication(OperationMode mode, int &argc, char **argv) : ApplicationWaylandAbstract(mode, argc, argv) { QStandardPaths::setTestModeEnabled(true); // TODO: add a test move to kglobalaccel instead? QFile{QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("kglobalshortcutsrc"))}.remove(); QIcon::setThemeName(QStringLiteral("breeze")); #ifdef KWIN_BUILD_ACTIVITIES setUseKActivities(false); #endif qputenv("KWIN_COMPOSE", QByteArrayLiteral("Q")); qunsetenv("XKB_DEFAULT_RULES"); qunsetenv("XKB_DEFAULT_MODEL"); qunsetenv("XKB_DEFAULT_LAYOUT"); qunsetenv("XKB_DEFAULT_VARIANT"); qunsetenv("XKB_DEFAULT_OPTIONS"); const auto ownPath = libraryPaths().last(); removeLibraryPath(ownPath); addLibraryPath(ownPath); const auto plugins = KPluginLoader::findPluginsById(QStringLiteral("org.kde.kwin.waylandbackends"), "KWinWaylandVirtualBackend"); if (plugins.empty()) { quit(); return; } initPlatform(plugins.first()); WaylandServer::create(this); setProcessStartupEnvironment(QProcessEnvironment::systemEnvironment()); } WaylandTestApplication::~WaylandTestApplication() { setTerminating(); kwinApp()->platform()->setOutputsEnabled(false); // need to unload all effects prior to destroying X connection as they might do X calls // also before destroy Workspace, as effects might call into Workspace if (effects) { static_cast(effects)->unloadAllEffects(); } + if (m_xwayland) { + // needs to be done before workspace gets destroyed + m_xwayland->prepareDestroy(); + } destroyWorkspace(); waylandServer()->dispatch(); if (QStyle *s = style()) { s->unpolish(this); } // kill Xwayland before terminating its connection delete m_xwayland; waylandServer()->terminateClientConnections(); destroyCompositor(); } void WaylandTestApplication::performStartup() { // first load options - done internally by a different thread createOptions(); waylandServer()->createInternalConnection(); // try creating the Wayland Backend createInput(); createBackend(); } void WaylandTestApplication::createBackend() { Platform *platform = kwinApp()->platform(); connect(platform, &Platform::screensQueried, this, &WaylandTestApplication::continueStartupWithScreens); connect(platform, &Platform::initFailed, this, [] () { std::cerr << "FATAL ERROR: backend failed to initialize, exiting now" << std::endl; ::exit(1); } ); platform->init(); } void WaylandTestApplication::continueStartupWithScreens() { disconnect(kwinApp()->platform(), &Platform::screensQueried, this, &WaylandTestApplication::continueStartupWithScreens); createScreens(); if (operationMode() == OperationModeWaylandOnly) { createCompositor(); connect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::continueStartupWithSceen); return; } createCompositor(); connect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::continueStartupWithXwayland); } void WaylandTestApplication::continueStartupWithSceen() { disconnect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::continueStartupWithSceen); createWorkspace(); } void WaylandTestApplication::continueStartupWithXwayland() { disconnect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::continueStartupWithXwayland); m_xwayland = new Xwl::Xwayland(this); connect(m_xwayland, &Xwl::Xwayland::criticalError, this, [](int code) { // we currently exit on Xwayland errors always directly // TODO: restart Xwayland std::cerr << "Xwayland had a critical error. Going to exit now." << std::endl; exit(code); }); m_xwayland->init(); } } diff --git a/autotests/integration/xclipboardsync_test.cpp b/autotests/integration/xclipboardsync_test.cpp index 54f4c8211..bf0db4869 100644 --- a/autotests/integration/xclipboardsync_test.cpp +++ b/autotests/integration/xclipboardsync_test.cpp @@ -1,186 +1,196 @@ /******************************************************************** 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 "kwin_wayland_test.h" #include "platform.h" #include "shell_client.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" +#include "../../xwl/databridge.h" #include #include #include using namespace KWin; static const QString s_socketName = QStringLiteral("wayland_test_kwin_xclipboard_sync-0"); class XClipboardSyncTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanup(); void testSync_data(); void testSync(); private: QProcess *m_copyProcess = nullptr; QProcess *m_pasteProcess = nullptr; }; void XClipboardSyncTest::initTestCase() { QSKIP("Skipped as it fails for unknown reasons on build.kde.org"); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); QSignalSpy clipboardSyncDevicedCreated{waylandServer(), &WaylandServer::xclipboardSyncDataDeviceCreated}; QVERIFY(clipboardSyncDevicedCreated.isValid()); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); waylandServer()->initWorkspace(); - // wait till the xclipboard sync data device is created - if (clipboardSyncDevicedCreated.empty()) { - QVERIFY(clipboardSyncDevicedCreated.wait()); +// // wait till the xclipboard sync data device is created +// if (clipboardSyncDevicedCreated.empty()) { +// QVERIFY(clipboardSyncDevicedCreated.wait()); +// } + // wait till the DataBridge sync data device is created + while (Xwl::DataBridge::self()->dataDeviceIface() == nullptr) { + QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); } - QVERIFY(!waylandServer()->xclipboardSyncDataDevice().isNull()); + QVERIFY(Xwl::DataBridge::self()->dataDeviceIface() != nullptr); } void XClipboardSyncTest::cleanup() { if (m_copyProcess) { m_copyProcess->terminate(); QVERIFY(m_copyProcess->waitForFinished()); m_copyProcess = nullptr; } if (m_pasteProcess) { m_pasteProcess->terminate(); QVERIFY(m_pasteProcess->waitForFinished()); m_pasteProcess = nullptr; } } void XClipboardSyncTest::testSync_data() { QTest::addColumn("copyPlatform"); QTest::addColumn("pastePlatform"); - QTest::newRow("x11-wayland") << QStringLiteral("xcb") << QStringLiteral("wayland"); - QTest::newRow("wayland-x11") << QStringLiteral("wayland") << QStringLiteral("xcb"); + QTest::newRow("x11->wayland") << QStringLiteral("xcb") << QStringLiteral("wayland"); + QTest::newRow("wayland->x11") << QStringLiteral("wayland") << QStringLiteral("xcb"); } void XClipboardSyncTest::testSync() { // this test verifies the syncing of X11 to Wayland clipboard const QString copy = QFINDTESTDATA(QStringLiteral("copy")); QVERIFY(!copy.isEmpty()); const QString paste = QFINDTESTDATA(QStringLiteral("paste")); QVERIFY(!paste.isEmpty()); QSignalSpy clientAddedSpy(workspace(), &Workspace::clientAdded); QVERIFY(clientAddedSpy.isValid()); QSignalSpy shellClientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(shellClientAddedSpy.isValid()); - QSignalSpy clipboardChangedSpy(waylandServer()->xclipboardSyncDataDevice().data(), &KWayland::Server::DataDeviceInterface::selectionChanged); + QSignalSpy clipboardChangedSpy(Xwl::DataBridge::self()->dataDeviceIface(), &KWayland::Server::DataDeviceInterface::selectionChanged); QVERIFY(clipboardChangedSpy.isValid()); QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); + + // start the copy process QFETCH(QString, copyPlatform); environment.insert(QStringLiteral("QT_QPA_PLATFORM"), copyPlatform); environment.insert(QStringLiteral("WAYLAND_DISPLAY"), s_socketName); m_copyProcess = new QProcess(); m_copyProcess->setProcessEnvironment(environment); m_copyProcess->setProcessChannelMode(QProcess::ForwardedChannels); m_copyProcess->setProgram(copy); m_copyProcess->start(); QVERIFY(m_copyProcess->waitForStarted()); AbstractClient *copyClient = nullptr; if (copyPlatform == QLatin1String("xcb")) { QVERIFY(clientAddedSpy.wait()); copyClient = clientAddedSpy.first().first().value(); } else { QVERIFY(shellClientAddedSpy.wait()); copyClient = shellClientAddedSpy.first().first().value(); } QVERIFY(copyClient); if (workspace()->activeClient() != copyClient) { workspace()->activateClient(copyClient); } QCOMPARE(workspace()->activeClient(), copyClient); if (copyPlatform == QLatin1String("xcb")) { QVERIFY(clipboardChangedSpy.isEmpty()); QVERIFY(clipboardChangedSpy.wait()); } else { // TODO: it would be better to be able to connect to a signal, instead of waiting // the idea is to make sure that the clipboard is updated, thus we need to give it // enough time before starting the paste process which creates another window QTest::qWait(250); } // start the paste process m_pasteProcess = new QProcess(); QSignalSpy finishedSpy(m_pasteProcess, static_cast(&QProcess::finished)); QVERIFY(finishedSpy.isValid()); QFETCH(QString, pastePlatform); environment.insert(QStringLiteral("QT_QPA_PLATFORM"), pastePlatform); m_pasteProcess->setProcessEnvironment(environment); m_pasteProcess->setProcessChannelMode(QProcess::ForwardedChannels); m_pasteProcess->setProgram(paste); m_pasteProcess->start(); QVERIFY(m_pasteProcess->waitForStarted()); AbstractClient *pasteClient = nullptr; if (pastePlatform == QLatin1String("xcb")) { QVERIFY(clientAddedSpy.wait()); pasteClient = clientAddedSpy.last().first().value(); } else { QVERIFY(shellClientAddedSpy.wait()); pasteClient = shellClientAddedSpy.last().first().value(); } QCOMPARE(clientAddedSpy.count(), 1); QCOMPARE(shellClientAddedSpy.count(), 1); QVERIFY(pasteClient); + if (workspace()->activeClient() != pasteClient) { QSignalSpy clientActivatedSpy(workspace(), &Workspace::clientActivated); QVERIFY(clientActivatedSpy.isValid()); workspace()->activateClient(pasteClient); QVERIFY(clientActivatedSpy.wait()); } QTRY_COMPARE(workspace()->activeClient(), pasteClient); QVERIFY(finishedSpy.wait()); QCOMPARE(finishedSpy.first().first().toInt(), 0); delete m_pasteProcess; m_pasteProcess = nullptr; + delete m_copyProcess; + m_copyProcess = nullptr; } WAYLANDTEST_MAIN(XClipboardSyncTest) #include "xclipboardsync_test.moc" diff --git a/main_wayland.cpp b/main_wayland.cpp index 72f368b97..1643ce7b0 100644 --- a/main_wayland.cpp +++ b/main_wayland.cpp @@ -1,659 +1,663 @@ /******************************************************************** 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 "main_wayland.h" #include "composite.h" #include "virtualkeyboard.h" #include "workspace.h" #include // kwin #include "platform.h" #include "effects.h" #include "tabletmodemanager.h" #include "wayland_server.h" #include "xwl/xwayland.h" // KWayland #include #include // KDE #include #include #include #include #include // Qt #include #include #include #include #include #include #include // system #if HAVE_SYS_PRCTL_H #include #endif #if HAVE_SYS_PROCCTL_H #include #endif #if HAVE_LIBCAP #include #endif #include #include #include namespace KWin { static void sighandler(int) { QApplication::exit(); } void disableDrKonqi() { KCrash::setDrKonqiEnabled(false); } // run immediately, before Q_CORE_STARTUP functions // that would enable drkonqi Q_CONSTRUCTOR_FUNCTION(disableDrKonqi) enum class RealTimeFlags { DontReset, ResetOnFork }; namespace { void gainRealTime(RealTimeFlags flags = RealTimeFlags::DontReset) { #if HAVE_SCHED_RESET_ON_FORK const int minPriority = sched_get_priority_min(SCHED_RR); struct sched_param sp; sp.sched_priority = minPriority; int policy = SCHED_RR; if (flags == RealTimeFlags::ResetOnFork) { policy |= SCHED_RESET_ON_FORK; } sched_setscheduler(0, policy, &sp); #else Q_UNUSED(flags); #endif } } //************************************ // ApplicationWayland //************************************ ApplicationWayland::ApplicationWayland(int &argc, char **argv) : ApplicationWaylandAbstract(OperationModeWaylandOnly, argc, argv) { } ApplicationWayland::~ApplicationWayland() { setTerminating(); if (!waylandServer()) { return; } if (kwinApp()->platform()) { kwinApp()->platform()->setOutputsEnabled(false); } // need to unload all effects prior to destroying X connection as they might do X calls if (effects) { static_cast(effects)->unloadAllEffects(); } + if (m_xwayland) { + // needs to be done before workspace gets destroyed + m_xwayland->prepareDestroy(); + } destroyWorkspace(); waylandServer()->dispatch(); if (QStyle *s = style()) { s->unpolish(this); } // kill Xwayland before terminating its connection delete m_xwayland; m_xwayland = nullptr; waylandServer()->terminateClientConnections(); destroyCompositor(); } void ApplicationWayland::performStartup() { if (m_startXWayland) { setOperationMode(OperationModeXwayland); } // first load options - done internally by a different thread createOptions(); waylandServer()->createInternalConnection(); // try creating the Wayland Backend createInput(); // now libinput thread has been created, adjust scheduler to not leak into other processes gainRealTime(RealTimeFlags::ResetOnFork); VirtualKeyboard::create(this); createBackend(); TabletModeManager::create(this); } void ApplicationWayland::createBackend() { connect(platform(), &Platform::screensQueried, this, &ApplicationWayland::continueStartupWithScreens); connect(platform(), &Platform::initFailed, this, [] () { std::cerr << "FATAL ERROR: backend failed to initialize, exiting now" << std::endl; QCoreApplication::exit(1); } ); platform()->init(); } void ApplicationWayland::continueStartupWithScreens() { disconnect(kwinApp()->platform(), &Platform::screensQueried, this, &ApplicationWayland::continueStartupWithScreens); createScreens(); if (operationMode() == OperationModeWaylandOnly) { createCompositor(); connect(Compositor::self(), &Compositor::sceneCreated, this, &ApplicationWayland::continueStartupWithSceen); return; } createCompositor(); connect(Compositor::self(), &Compositor::sceneCreated, this, &ApplicationWayland::continueStartupWithXwayland); } void ApplicationWayland::continueStartupWithSceen() { disconnect(Compositor::self(), &Compositor::sceneCreated, this, &ApplicationWayland::continueStartupWithSceen); startSession(); createWorkspace(); notifyKSplash(); } void ApplicationWayland::continueStartupWithXwayland() { disconnect(Compositor::self(), &Compositor::sceneCreated, this, &ApplicationWayland::continueStartupWithXwayland); m_xwayland = new Xwl::Xwayland(this); connect(m_xwayland, &Xwl::Xwayland::criticalError, this, [](int code) { // we currently exit on Xwayland errors always directly // TODO: restart Xwayland std::cerr << "Xwayland had a critical error. Going to exit now." << std::endl; exit(code); }); m_xwayland->init(); } void ApplicationWayland::startSession() { if (!m_inputMethodServerToStart.isEmpty()) { int socket = dup(waylandServer()->createInputMethodConnection()); if (socket >= 0) { QProcessEnvironment environment = processStartupEnvironment(); environment.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket)); environment.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("wayland")); environment.remove("DISPLAY"); environment.remove("WAYLAND_DISPLAY"); QProcess *p = new Process(this); p->setProcessChannelMode(QProcess::ForwardedErrorChannel); auto finishedSignal = static_cast(&QProcess::finished); connect(p, finishedSignal, this, [this, p] { if (waylandServer()) { waylandServer()->destroyInputMethodConnection(); } p->deleteLater(); } ); p->setProcessEnvironment(environment); p->start(m_inputMethodServerToStart); p->waitForStarted(); } } // start session if (!m_sessionArgument.isEmpty()) { QProcess *p = new Process(this); p->setProcessChannelMode(QProcess::ForwardedErrorChannel); p->setProcessEnvironment(processStartupEnvironment()); auto finishedSignal = static_cast(&QProcess::finished); connect(p, finishedSignal, this, &ApplicationWayland::quit); p->start(m_sessionArgument); } // start the applications passed to us as command line arguments if (!m_applicationsToStart.isEmpty()) { for (const QString &application: m_applicationsToStart) { // note: this will kill the started process when we exit // this is going to happen anyway as we are the wayland and X server the app connects to QProcess *p = new Process(this); p->setProcessChannelMode(QProcess::ForwardedErrorChannel); p->setProcessEnvironment(processStartupEnvironment()); p->start(application); } } } static const QString s_waylandPlugin = QStringLiteral("KWinWaylandWaylandBackend"); static const QString s_x11Plugin = QStringLiteral("KWinWaylandX11Backend"); static const QString s_fbdevPlugin = QStringLiteral("KWinWaylandFbdevBackend"); #if HAVE_DRM static const QString s_drmPlugin = QStringLiteral("KWinWaylandDrmBackend"); #endif #if HAVE_LIBHYBRIS static const QString s_hwcomposerPlugin = QStringLiteral("KWinWaylandHwcomposerBackend"); #endif static const QString s_virtualPlugin = QStringLiteral("KWinWaylandVirtualBackend"); static QString automaticBackendSelection() { if (qEnvironmentVariableIsSet("WAYLAND_DISPLAY")) { return s_waylandPlugin; } if (qEnvironmentVariableIsSet("DISPLAY")) { return s_x11Plugin; } #if HAVE_LIBHYBRIS if (qEnvironmentVariableIsSet("ANDROID_ROOT")) { return s_hwcomposerPlugin; } #endif #if HAVE_DRM return s_drmPlugin; #endif return s_fbdevPlugin; } static void disablePtrace() { #if HAVE_PR_SET_DUMPABLE // check whether we are running under a debugger const QFileInfo parent(QStringLiteral("/proc/%1/exe").arg(getppid())); if (parent.isSymLink() && (parent.symLinkTarget().endsWith(QLatin1String("/gdb")) || parent.symLinkTarget().endsWith(QLatin1String("/gdbserver")))) { // debugger, don't adjust return; } // disable ptrace in kwin_wayland prctl(PR_SET_DUMPABLE, 0); #endif #if HAVE_PROC_TRACE_CTL // FreeBSD's rudimentary procfs does not support /proc//exe // We could use the P_TRACED flag of the process to find out // if the process is being debugged ond FreeBSD. int mode = PROC_TRACE_CTL_DISABLE; procctl(P_PID, getpid(), PROC_TRACE_CTL, &mode); #endif } static void unsetDumpable(int sig) { #if HAVE_PR_SET_DUMPABLE prctl(PR_SET_DUMPABLE, 1); #endif signal(sig, SIG_IGN); raise(sig); return; } void dropNiceCapability() { #if HAVE_LIBCAP cap_t caps = cap_get_proc(); if (!caps) { return; } cap_value_t capList[] = { CAP_SYS_NICE }; if (cap_set_flag(caps, CAP_PERMITTED, 1, capList, CAP_CLEAR) == -1) { cap_free(caps); return; } if (cap_set_flag(caps, CAP_EFFECTIVE, 1, capList, CAP_CLEAR) == -1) { cap_free(caps); return; } cap_set_proc(caps); cap_free(caps); #endif } } // namespace int main(int argc, char * argv[]) { if (getuid() == 0) { std::cerr << "kwin_wayland does not support running as root." << std::endl; return 1; } KWin::disablePtrace(); KWin::Application::setupMalloc(); KWin::Application::setupLocalizedString(); KWin::gainRealTime(); KWin::dropNiceCapability(); if (signal(SIGTERM, KWin::sighandler) == SIG_IGN) signal(SIGTERM, SIG_IGN); if (signal(SIGINT, KWin::sighandler) == SIG_IGN) signal(SIGINT, SIG_IGN); if (signal(SIGHUP, KWin::sighandler) == SIG_IGN) signal(SIGHUP, SIG_IGN); signal(SIGABRT, KWin::unsetDumpable); signal(SIGSEGV, KWin::unsetDumpable); signal(SIGPIPE, SIG_IGN); // ensure that no thread takes SIGUSR sigset_t userSignals; sigemptyset(&userSignals); sigaddset(&userSignals, SIGUSR1); sigaddset(&userSignals, SIGUSR2); pthread_sigmask(SIG_BLOCK, &userSignals, nullptr); QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); // enforce our internal qpa plugin, unfortunately command line switch has precedence setenv("QT_QPA_PLATFORM", "wayland-org.kde.kwin.qpa", true); qunsetenv("QT_DEVICE_PIXEL_RATIO"); qputenv("QT_IM_MODULE", "qtvirtualkeyboard"); qputenv("QSG_RENDER_LOOP", "basic"); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); KWin::ApplicationWayland a(argc, argv); a.setupTranslator(); // reset QT_QPA_PLATFORM to a sane value for any processes started from KWin setenv("QT_QPA_PLATFORM", "wayland", true); KWin::Application::createAboutData(); KQuickAddons::QtQuickSettings::init(); const auto availablePlugins = KPluginLoader::findPlugins(QStringLiteral("org.kde.kwin.waylandbackends")); auto hasPlugin = [&availablePlugins] (const QString &name) { return std::any_of(availablePlugins.begin(), availablePlugins.end(), [name] (const KPluginMetaData &plugin) { return plugin.pluginId() == name; } ); }; const bool hasSizeOption = hasPlugin(KWin::s_x11Plugin) || hasPlugin(KWin::s_virtualPlugin); const bool hasOutputCountOption = hasPlugin(KWin::s_x11Plugin); const bool hasX11Option = hasPlugin(KWin::s_x11Plugin); const bool hasVirtualOption = hasPlugin(KWin::s_virtualPlugin); const bool hasWaylandOption = hasPlugin(KWin::s_waylandPlugin); const bool hasFramebufferOption = hasPlugin(KWin::s_fbdevPlugin); #if HAVE_DRM const bool hasDrmOption = hasPlugin(KWin::s_drmPlugin); #endif #if HAVE_LIBHYBRIS const bool hasHwcomposerOption = hasPlugin(KWin::s_hwcomposerPlugin); #endif QCommandLineOption xwaylandOption(QStringLiteral("xwayland"), i18n("Start a rootless Xwayland server.")); QCommandLineOption waylandSocketOption(QStringList{QStringLiteral("s"), QStringLiteral("socket")}, i18n("Name of the Wayland socket to listen on. If not set \"wayland-0\" is used."), QStringLiteral("socket")); QCommandLineOption framebufferOption(QStringLiteral("framebuffer"), i18n("Render to framebuffer.")); QCommandLineOption framebufferDeviceOption(QStringLiteral("fb-device"), i18n("The framebuffer device to render to."), QStringLiteral("fbdev")); QCommandLineOption x11DisplayOption(QStringLiteral("x11-display"), i18n("The X11 Display to use in windowed mode on platform X11."), QStringLiteral("display")); QCommandLineOption waylandDisplayOption(QStringLiteral("wayland-display"), i18n("The Wayland Display to use in windowed mode on platform Wayland."), QStringLiteral("display")); QCommandLineOption virtualFbOption(QStringLiteral("virtual"), i18n("Render to a virtual framebuffer.")); QCommandLineOption widthOption(QStringLiteral("width"), i18n("The width for windowed mode. Default width is 1024."), QStringLiteral("width")); widthOption.setDefaultValue(QString::number(1024)); QCommandLineOption heightOption(QStringLiteral("height"), i18n("The height for windowed mode. Default height is 768."), QStringLiteral("height")); heightOption.setDefaultValue(QString::number(768)); QCommandLineOption scaleOption(QStringLiteral("scale"), i18n("The scale for windowed mode. Default value is 1."), QStringLiteral("scale")); scaleOption.setDefaultValue(QString::number(1)); QCommandLineOption outputCountOption(QStringLiteral("output-count"), i18n("The number of windows to open as outputs in windowed mode. Default value is 1"), QStringLiteral("height")); outputCountOption.setDefaultValue(QString::number(1)); QCommandLineParser parser; a.setupCommandLine(&parser); parser.addOption(xwaylandOption); parser.addOption(waylandSocketOption); if (hasX11Option) { parser.addOption(x11DisplayOption); } if (hasWaylandOption) { parser.addOption(waylandDisplayOption); } if (hasFramebufferOption) { parser.addOption(framebufferOption); parser.addOption(framebufferDeviceOption); } if (hasVirtualOption) { parser.addOption(virtualFbOption); } if (hasSizeOption) { parser.addOption(widthOption); parser.addOption(heightOption); parser.addOption(scaleOption); } if (hasOutputCountOption) { parser.addOption(outputCountOption); } #if HAVE_LIBHYBRIS QCommandLineOption hwcomposerOption(QStringLiteral("hwcomposer"), i18n("Use libhybris hwcomposer")); if (hasHwcomposerOption) { parser.addOption(hwcomposerOption); } #endif QCommandLineOption libinputOption(QStringLiteral("libinput"), i18n("Enable libinput support for input events processing. Note: never use in a nested session.")); parser.addOption(libinputOption); #if HAVE_DRM QCommandLineOption drmOption(QStringLiteral("drm"), i18n("Render through drm node.")); if (hasDrmOption) { parser.addOption(drmOption); } #endif QCommandLineOption inputMethodOption(QStringLiteral("inputmethod"), i18n("Input method that KWin starts."), QStringLiteral("path/to/imserver")); parser.addOption(inputMethodOption); QCommandLineOption listBackendsOption(QStringLiteral("list-backends"), i18n("List all available backends and quit.")); parser.addOption(listBackendsOption); QCommandLineOption screenLockerOption(QStringLiteral("lockscreen"), i18n("Starts the session in locked mode.")); parser.addOption(screenLockerOption); QCommandLineOption noScreenLockerOption(QStringLiteral("no-lockscreen"), i18n("Starts the session without lock screen support.")); parser.addOption(noScreenLockerOption); QCommandLineOption noGlobalShortcutsOption(QStringLiteral("no-global-shortcuts"), i18n("Starts the session without global shortcuts support.")); parser.addOption(noGlobalShortcutsOption); QCommandLineOption exitWithSessionOption(QStringLiteral("exit-with-session"), i18n("Exit after the session application, which is started by KWin, closed."), QStringLiteral("/path/to/session")); parser.addOption(exitWithSessionOption); parser.addPositionalArgument(QStringLiteral("applications"), i18n("Applications to start once Wayland and Xwayland server are started"), QStringLiteral("[/path/to/application...]")); parser.process(a); a.processCommandLine(&parser); #ifdef KWIN_BUILD_ACTIVITIES a.setUseKActivities(false); #endif if (parser.isSet(listBackendsOption)) { for (const auto &plugin: availablePlugins) { std::cout << std::setw(40) << std::left << qPrintable(plugin.name()) << qPrintable(plugin.description()) << std::endl; } return 0; } if (parser.isSet(exitWithSessionOption)) { a.setSessionArgument(parser.value(exitWithSessionOption)); } KWin::Application::setUseLibinput(parser.isSet(libinputOption)); QString pluginName; QSize initialWindowSize; QByteArray deviceIdentifier; int outputCount = 1; qreal outputScale = 1; #if HAVE_DRM if (hasDrmOption && parser.isSet(drmOption)) { pluginName = KWin::s_drmPlugin; } #endif if (hasSizeOption) { bool ok = false; const int width = parser.value(widthOption).toInt(&ok); if (!ok) { std::cerr << "FATAL ERROR incorrect value for width" << std::endl; return 1; } const int height = parser.value(heightOption).toInt(&ok); if (!ok) { std::cerr << "FATAL ERROR incorrect value for height" << std::endl; return 1; } const qreal scale = parser.value(scaleOption).toDouble(&ok); if (!ok || scale < 1) { std::cerr << "FATAL ERROR incorrect value for scale" << std::endl; return 1; } outputScale = scale; initialWindowSize = QSize(width, height); } if (hasOutputCountOption) { bool ok = false; const int count = parser.value(outputCountOption).toInt(&ok); if (ok) { outputCount = qMax(1, count); } } if (hasX11Option && parser.isSet(x11DisplayOption)) { deviceIdentifier = parser.value(x11DisplayOption).toUtf8(); pluginName = KWin::s_x11Plugin; } else if (hasWaylandOption && parser.isSet(waylandDisplayOption)) { deviceIdentifier = parser.value(waylandDisplayOption).toUtf8(); pluginName = KWin::s_waylandPlugin; } if (hasFramebufferOption && parser.isSet(framebufferOption)) { pluginName = KWin::s_fbdevPlugin; deviceIdentifier = parser.value(framebufferDeviceOption).toUtf8(); } #if HAVE_LIBHYBRIS if (hasHwcomposerOption && parser.isSet(hwcomposerOption)) { pluginName = KWin::s_hwcomposerPlugin; } #endif if (hasVirtualOption && parser.isSet(virtualFbOption)) { pluginName = KWin::s_virtualPlugin; } if (pluginName.isEmpty()) { std::cerr << "No backend specified through command line argument, trying auto resolution" << std::endl; pluginName = KWin::automaticBackendSelection(); } auto pluginIt = std::find_if(availablePlugins.begin(), availablePlugins.end(), [&pluginName] (const KPluginMetaData &plugin) { return plugin.pluginId() == pluginName; } ); if (pluginIt == availablePlugins.end()) { std::cerr << "FATAL ERROR: could not find a backend" << std::endl; return 1; } // TODO: create backend without having the server running KWin::WaylandServer *server = KWin::WaylandServer::create(&a); KWin::WaylandServer::InitalizationFlags flags; if (parser.isSet(screenLockerOption)) { flags = KWin::WaylandServer::InitalizationFlag::LockScreen; } else if (parser.isSet(noScreenLockerOption)) { flags = KWin::WaylandServer::InitalizationFlag::NoLockScreenIntegration; } if (parser.isSet(noGlobalShortcutsOption)) { flags |= KWin::WaylandServer::InitalizationFlag::NoGlobalShortcuts; } if (!server->init(parser.value(waylandSocketOption).toUtf8(), flags)) { std::cerr << "FATAL ERROR: could not create Wayland server" << std::endl; return 1; } a.initPlatform(*pluginIt); if (!a.platform()) { std::cerr << "FATAL ERROR: could not instantiate a backend" << std::endl; return 1; } if (!deviceIdentifier.isEmpty()) { a.platform()->setDeviceIdentifier(deviceIdentifier); } if (initialWindowSize.isValid()) { a.platform()->setInitialWindowSize(initialWindowSize); } a.platform()->setInitialOutputScale(outputScale); a.platform()->setInitialOutputCount(outputCount); QObject::connect(&a, &KWin::Application::workspaceCreated, server, &KWin::WaylandServer::initWorkspace); environment.insert(QStringLiteral("WAYLAND_DISPLAY"), server->display()->socketName()); a.setProcessStartupEnvironment(environment); a.setStartXwayland(parser.isSet(xwaylandOption)); a.setApplicationsToStart(parser.positionalArguments()); a.setInputMethodServerToStart(parser.value(inputMethodOption)); a.start(); return a.exec(); } diff --git a/wayland_server.cpp b/wayland_server.cpp index f88d98f37..df84d707c 100644 --- a/wayland_server.cpp +++ b/wayland_server.cpp @@ -1,813 +1,826 @@ /******************************************************************** 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 "wayland_server.h" #include "client.h" #include "platform.h" #include "composite.h" #include "idle_inhibition.h" #include "screens.h" #include "shell_client.h" #include "workspace.h" // Client #include #include #include +#include +#include #include #include // Server #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Qt #include #include #include #include // system #include #include #include //screenlocker #include using namespace KWayland::Server; namespace KWin { KWIN_SINGLETON_FACTORY(WaylandServer) WaylandServer::WaylandServer(QObject *parent) : QObject(parent) { qRegisterMetaType(); connect(kwinApp(), &Application::screensCreated, this, &WaylandServer::initOutputs); - connect(kwinApp(), &Application::x11ConnectionChanged, this, &WaylandServer::setupX11ClipboardSync); +// connect(kwinApp(), &Application::x11ConnectionChanged, this, &WaylandServer::setupX11ClipboardSync); } WaylandServer::~WaylandServer() { destroyInputMethodConnection(); } void WaylandServer::destroyInternalConnection() { emit terminatingInternalClientConnection(); if (m_internalConnection.client) { // delete all connections hold by plugins like e.g. widget style const auto connections = KWayland::Client::ConnectionThread::connections(); for (auto c : connections) { if (c == m_internalConnection.client) { continue; } emit c->connectionDied(); } delete m_internalConnection.registry; + delete m_internalConnection.seat; + delete m_internalConnection.ddm; delete m_internalConnection.shm; dispatch(); m_internalConnection.client->deleteLater(); m_internalConnection.clientThread->quit(); m_internalConnection.clientThread->wait(); delete m_internalConnection.clientThread; m_internalConnection.client = nullptr; m_internalConnection.server->destroy(); m_internalConnection.server = nullptr; } } void WaylandServer::terminateClientConnections() { destroyInternalConnection(); destroyInputMethodConnection(); if (m_display) { const auto connections = m_display->connections(); for (auto it = connections.begin(); it != connections.end(); ++it) { (*it)->destroy(); } } } template void WaylandServer::createSurface(T *surface) { if (!Workspace::self()) { // it's possible that a Surface gets created before Workspace is created return; } if (surface->client() == m_xwayland.client) { // skip Xwayland clients, those are created using standard X11 way return; } if (surface->client() == m_screenLockerClientConnection) { ScreenLocker::KSldApp::self()->lockScreenShown(); } auto client = new ShellClient(surface); if (ServerSideDecorationInterface *deco = ServerSideDecorationInterface::get(surface->surface())) { client->installServerSideDecoration(deco); } auto it = std::find_if(m_plasmaShellSurfaces.begin(), m_plasmaShellSurfaces.end(), [client] (PlasmaShellSurfaceInterface *surface) { return client->surface() == surface->surface(); } ); if (it != m_plasmaShellSurfaces.end()) { client->installPlasmaShellSurface(*it); m_plasmaShellSurfaces.erase(it); } if (auto menu = m_appMenuManager->appMenuForSurface(surface->surface())) { client->installAppMenu(menu); } if (auto palette = m_paletteManager->paletteForSurface(surface->surface())) { client->installPalette(palette); } if (client->isInternal()) { m_internalClients << client; } else { m_clients << client; } if (client->readyForPainting()) { emit shellClientAdded(client); } else { connect(client, &ShellClient::windowShown, this, &WaylandServer::shellClientShown); } //not directly connected as the connection is tied to client instead of this connect(m_XdgForeign, &KWayland::Server::XdgForeignInterface::transientChanged, client, [this](KWayland::Server::SurfaceInterface *child) { emit foreignTransientChanged(child); }); } bool WaylandServer::init(const QByteArray &socketName, InitalizationFlags flags) { m_initFlags = flags; m_display = new KWayland::Server::Display(this); if (!socketName.isNull() && !socketName.isEmpty()) { m_display->setSocketName(QString::fromUtf8(socketName)); } else { m_display->setAutomaticSocketNaming(true); } m_display->start(); if (!m_display->isRunning()) { return false; } m_compositor = m_display->createCompositor(m_display); m_compositor->create(); connect(m_compositor, &CompositorInterface::surfaceCreated, this, [this] (SurfaceInterface *surface) { // check whether we have a Toplevel with the Surface's id Workspace *ws = Workspace::self(); if (!ws) { // it's possible that a Surface gets created before Workspace is created return; } if (surface->client() != xWaylandConnection()) { // setting surface is only relevat for Xwayland clients return; } auto check = [surface] (const Toplevel *t) { return t->surfaceId() == surface->id(); }; if (Toplevel *t = ws->findToplevel(check)) { t->setSurface(surface); } } ); m_shell = m_display->createShell(m_display); m_shell->create(); connect(m_shell, &ShellInterface::surfaceCreated, this, &WaylandServer::createSurface); m_xdgShell5 = m_display->createXdgShell(XdgShellInterfaceVersion::UnstableV5, m_display); m_xdgShell5->create(); connect(m_xdgShell5, &XdgShellInterface::surfaceCreated, this, &WaylandServer::createSurface); // TODO: verify seat and serial connect(m_xdgShell5, &XdgShellInterface::popupCreated, this, &WaylandServer::createSurface); m_xdgShell6 = m_display->createXdgShell(XdgShellInterfaceVersion::UnstableV6, m_display); m_xdgShell6->create(); connect(m_xdgShell6, &XdgShellInterface::surfaceCreated, this, &WaylandServer::createSurface); connect(m_xdgShell6, &XdgShellInterface::xdgPopupCreated, this, &WaylandServer::createSurface); m_xdgShell = m_display->createXdgShell(XdgShellInterfaceVersion::Stable, m_display); m_xdgShell->create(); connect(m_xdgShell, &XdgShellInterface::surfaceCreated, this, &WaylandServer::createSurface); connect(m_xdgShell, &XdgShellInterface::xdgPopupCreated, this, &WaylandServer::createSurface); m_xdgDecorationManager = m_display->createXdgDecorationManager(m_xdgShell, m_display); m_xdgDecorationManager->create(); connect(m_xdgDecorationManager, &XdgDecorationManagerInterface::xdgDecorationInterfaceCreated, this, [this] (XdgDecorationInterface *deco) { if (ShellClient *client = findClient(deco->surface()->surface())) { client->installXdgDecoration(deco); } }); m_display->createShm(); m_seat = m_display->createSeat(m_display); m_seat->create(); m_display->createPointerGestures(PointerGesturesInterfaceVersion::UnstableV1, m_display)->create(); m_display->createPointerConstraints(PointerConstraintsInterfaceVersion::UnstableV1, m_display)->create(); - auto ddm = m_display->createDataDeviceManager(m_display); - ddm->create(); - connect(ddm, &DataDeviceManagerInterface::dataDeviceCreated, this, + m_dataDeviceManager = m_display->createDataDeviceManager(m_display); + m_dataDeviceManager->create(); + connect(m_dataDeviceManager, &DataDeviceManagerInterface::dataDeviceCreated, this, [this] (DataDeviceInterface *ddi) { if (ddi->client() == m_xclipbaordSync.client && m_xclipbaordSync.client != nullptr) { m_xclipbaordSync.ddi = QPointer(ddi); emit xclipboardSyncDataDeviceCreated(); connect(m_xclipbaordSync.ddi.data(), &DataDeviceInterface::selectionChanged, this, [this] { // testing whether the active client inherits Client // it would be better to test for the keyboard focus, but we might get a clipboard update // when the Client is already active, but no Surface is created yet. if (workspace()->activeClient() && workspace()->activeClient()->inherits("KWin::Client")) { m_seat->setSelection(m_xclipbaordSync.ddi.data()); } } ); } } ); m_idle = m_display->createIdle(m_display); m_idle->create(); auto idleInhibition = new IdleInhibition(m_idle); connect(this, &WaylandServer::shellClientAdded, idleInhibition, &IdleInhibition::registerShellClient); m_display->createIdleInhibitManager(IdleInhibitManagerInterfaceVersion::UnstableV1, m_display)->create(); m_plasmaShell = m_display->createPlasmaShell(m_display); m_plasmaShell->create(); connect(m_plasmaShell, &PlasmaShellInterface::surfaceCreated, [this] (PlasmaShellSurfaceInterface *surface) { if (ShellClient *client = findClient(surface->surface())) { client->installPlasmaShellSurface(surface); } else { m_plasmaShellSurfaces << surface; connect(surface, &QObject::destroyed, this, [this, surface] { m_plasmaShellSurfaces.removeOne(surface); } ); } } ); m_appMenuManager = m_display->createAppMenuManagerInterface(m_display); m_appMenuManager->create(); connect(m_appMenuManager, &AppMenuManagerInterface::appMenuCreated, [this] (AppMenuInterface *appMenu) { if (ShellClient *client = findClient(appMenu->surface())) { client->installAppMenu(appMenu); } } ); m_paletteManager = m_display->createServerSideDecorationPaletteManager(m_display); m_paletteManager->create(); connect(m_paletteManager, &ServerSideDecorationPaletteManagerInterface::paletteCreated, [this] (ServerSideDecorationPaletteInterface *palette) { if (ShellClient *client = findClient(palette->surface())) { client->installPalette(palette); } } ); m_windowManagement = m_display->createPlasmaWindowManagement(m_display); m_windowManagement->create(); m_windowManagement->setShowingDesktopState(PlasmaWindowManagementInterface::ShowingDesktopState::Disabled); connect(m_windowManagement, &PlasmaWindowManagementInterface::requestChangeShowingDesktop, this, [] (PlasmaWindowManagementInterface::ShowingDesktopState state) { if (!workspace()) { return; } bool set = false; switch (state) { case PlasmaWindowManagementInterface::ShowingDesktopState::Disabled: set = false; break; case PlasmaWindowManagementInterface::ShowingDesktopState::Enabled: set = true; break; default: Q_UNREACHABLE(); break; } if (set == workspace()->showingDesktop()) { return; } workspace()->setShowingDesktop(set); } ); m_virtualDesktopManagement = m_display->createPlasmaVirtualDesktopManagement(m_display); m_virtualDesktopManagement->create(); m_windowManagement->setPlasmaVirtualDesktopManagementInterface(m_virtualDesktopManagement); auto shadowManager = m_display->createShadowManager(m_display); shadowManager->create(); m_display->createDpmsManager(m_display)->create(); m_decorationManager = m_display->createServerSideDecorationManager(m_display); connect(m_decorationManager, &ServerSideDecorationManagerInterface::decorationCreated, this, [this] (ServerSideDecorationInterface *deco) { if (ShellClient *c = findClient(deco->surface())) { c->installServerSideDecoration(deco); } connect(deco, &ServerSideDecorationInterface::modeRequested, this, [this, deco] (ServerSideDecorationManagerInterface::Mode mode) { // always acknowledge the requested mode deco->setMode(mode); } ); } ); m_decorationManager->create(); m_outputManagement = m_display->createOutputManagement(m_display); connect(m_outputManagement, &OutputManagementInterface::configurationChangeRequested, this, [this](KWayland::Server::OutputConfigurationInterface *config) { kwinApp()->platform()->configurationChangeRequested(config); }); m_outputManagement->create(); m_xdgOutputManager = m_display->createXdgOutputManager(m_display); m_xdgOutputManager->create(); m_display->createSubCompositor(m_display)->create(); m_XdgForeign = m_display->createXdgForeignInterface(m_display); m_XdgForeign->create(); return true; } SurfaceInterface *WaylandServer::findForeignTransientForSurface(SurfaceInterface *surface) { return m_XdgForeign->transientFor(surface); } void WaylandServer::shellClientShown(Toplevel *t) { ShellClient *c = dynamic_cast(t); if (!c) { qCWarning(KWIN_CORE) << "Failed to cast a Toplevel which is supposed to be a ShellClient to ShellClient"; return; } disconnect(c, &ShellClient::windowShown, this, &WaylandServer::shellClientShown); emit shellClientAdded(c); } void WaylandServer::initWorkspace() { VirtualDesktopManager::self()->setVirtualDesktopManagement(m_virtualDesktopManagement); if (m_windowManagement) { connect(workspace(), &Workspace::showingDesktopChanged, this, [this] (bool set) { using namespace KWayland::Server; m_windowManagement->setShowingDesktopState(set ? PlasmaWindowManagementInterface::ShowingDesktopState::Enabled : PlasmaWindowManagementInterface::ShowingDesktopState::Disabled ); } ); } if (hasScreenLockerIntegration()) { if (m_internalConnection.interfacesAnnounced) { initScreenLocker(); } else { connect(m_internalConnection.registry, &KWayland::Client::Registry::interfacesAnnounced, this, &WaylandServer::initScreenLocker); } } else { emit initialized(); } } void WaylandServer::initScreenLocker() { ScreenLocker::KSldApp::self(); ScreenLocker::KSldApp::self()->setWaylandDisplay(m_display); ScreenLocker::KSldApp::self()->setGreeterEnvironment(kwinApp()->processStartupEnvironment()); ScreenLocker::KSldApp::self()->initialize(); connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::greeterClientConnectionChanged, this, [this] () { m_screenLockerClientConnection = ScreenLocker::KSldApp::self()->greeterClientConnection(); } ); connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::unlocked, this, [this] () { m_screenLockerClientConnection = nullptr; } ); if (m_initFlags.testFlag(InitalizationFlag::LockScreen)) { ScreenLocker::KSldApp::self()->lock(ScreenLocker::EstablishLock::Immediate); } emit initialized(); } void WaylandServer::initOutputs() { if (kwinApp()->platform()->handlesOutputs()) { return; } syncOutputsToWayland(); connect(screens(), &Screens::changed, this, [this] { // when screens change we need to sync this to Wayland. // Unfortunately we don't have much information and cannot properly match a KWin screen // to a Wayland screen. // Thus we just recreate all outputs and delete the old ones const auto outputs = m_display->outputs(); syncOutputsToWayland(); qDeleteAll(outputs); } ); } void WaylandServer::syncOutputsToWayland() { Screens *s = screens(); Q_ASSERT(s); for (int i = 0; i < s->count(); ++i) { OutputInterface *output = m_display->createOutput(m_display); auto xdgOutput = xdgOutputManager()->createXdgOutput(output, output); output->setScale(s->scale(i)); const QRect &geo = s->geometry(i); output->setGlobalPosition(geo.topLeft()); output->setPhysicalSize(s->physicalSize(i).toSize()); output->addMode(geo.size()); xdgOutput->setLogicalPosition(geo.topLeft()); xdgOutput->setLogicalSize(geo.size()); xdgOutput->done(); output->create(); } } WaylandServer::SocketPairConnection WaylandServer::createConnection() { SocketPairConnection ret; int sx[2]; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { qCWarning(KWIN_CORE) << "Could not create socket"; return ret; } ret.connection = m_display->createClient(sx[0]); ret.fd = sx[1]; return ret; } int WaylandServer::createXWaylandConnection() { const auto socket = createConnection(); if (!socket.connection) { return -1; } m_xwayland.client = socket.connection; m_xwayland.destroyConnection = connect(m_xwayland.client, &KWayland::Server::ClientConnection::disconnected, this, [] { qFatal("Xwayland Connection died"); } ); return socket.fd; } void WaylandServer::destroyXWaylandConnection() { if (!m_xwayland.client) { return; } // first terminate the clipboard sync if (m_xclipbaordSync.process) { m_xclipbaordSync.process->terminate(); } disconnect(m_xwayland.destroyConnection); m_xwayland.client->destroy(); m_xwayland.client = nullptr; } int WaylandServer::createInputMethodConnection() { const auto socket = createConnection(); if (!socket.connection) { return -1; } m_inputMethodServerConnection = socket.connection; return socket.fd; } void WaylandServer::destroyInputMethodConnection() { if (!m_inputMethodServerConnection) { return; } m_inputMethodServerConnection->destroy(); m_inputMethodServerConnection = nullptr; } int WaylandServer::createXclipboardSyncConnection() { const auto socket = createConnection(); if (!socket.connection) { return -1; } m_xclipbaordSync.client = socket.connection; return socket.fd; } void WaylandServer::setupX11ClipboardSync() { if (m_xclipbaordSync.process) { qCWarning(KWIN_CORE) << "Tried to start x clipboard syncer although process already started"; return; } int socket = dup(createXclipboardSyncConnection()); if (socket == -1) { delete m_xclipbaordSync.client; m_xclipbaordSync.client = nullptr; qCWarning(KWIN_CORE) << "Could not create wayland socket for x clipboard syncer"; return; } if (socket >= 0) { QProcessEnvironment environment = kwinApp()->processStartupEnvironment(); environment.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket)); environment.insert(QStringLiteral("DISPLAY"), QString::fromUtf8(qgetenv("DISPLAY"))); environment.remove("WAYLAND_DISPLAY"); m_xclipbaordSync.process = new Process(this); m_xclipbaordSync.process->setProcessChannelMode(QProcess::ForwardedChannels); auto finishedSignal = static_cast(&QProcess::finished); connect(m_xclipbaordSync.process, finishedSignal, this, [this] { qCDebug(KWIN_CORE) << "X clipboard syncer process finished"; m_xclipbaordSync.process->deleteLater(); m_xclipbaordSync.process = nullptr; m_xclipbaordSync.ddi.clear(); m_xclipbaordSync.client->destroy(); m_xclipbaordSync.client = nullptr; // TODO: restart } ); m_xclipbaordSync.process->setProcessEnvironment(environment); // start from build directory if executable is available there (e.g. autotests), otherwise start libexec executable const QFileInfo clipboardSync{QDir{QCoreApplication::applicationDirPath()}, QStringLiteral("org_kde_kwin_xclipboard_syncer")}; if (clipboardSync.exists()) { qCDebug(KWIN_CORE) << "Starting" << clipboardSync.absoluteFilePath(); m_xclipbaordSync.process->start(clipboardSync.absoluteFilePath()); } else { qCDebug(KWIN_CORE) << "Starting" << KWIN_XCLIPBOARD_SYNC_BIN; m_xclipbaordSync.process->start(QStringLiteral(KWIN_XCLIPBOARD_SYNC_BIN)); } } } void WaylandServer::createInternalConnection() { const auto socket = createConnection(); if (!socket.connection) { return; } m_internalConnection.server = socket.connection; using namespace KWayland::Client; m_internalConnection.client = new ConnectionThread(); m_internalConnection.client->setSocketFd(socket.fd); m_internalConnection.clientThread = new QThread; m_internalConnection.client->moveToThread(m_internalConnection.clientThread); m_internalConnection.clientThread->start(); connect(m_internalConnection.client, &ConnectionThread::connected, this, [this] { Registry *registry = new Registry(this); EventQueue *eventQueue = new EventQueue(this); eventQueue->setup(m_internalConnection.client); registry->setEventQueue(eventQueue); registry->create(m_internalConnection.client); m_internalConnection.registry = registry; connect(registry, &Registry::shmAnnounced, this, [this] (quint32 name, quint32 version) { m_internalConnection.shm = m_internalConnection.registry->createShmPool(name, version, this); } ); connect(registry, &Registry::interfacesAnnounced, this, - [this] { + [this, registry] { m_internalConnection.interfacesAnnounced = true; + + const auto seatInterface = registry->interface(Registry::Interface::Seat); + if (seatInterface.name != 0) { + m_internalConnection.seat = registry->createSeat(seatInterface.name, seatInterface.version, this); + } + const auto ddmInterface = registry->interface(Registry::Interface::DataDeviceManager); + if (ddmInterface.name != 0) { + m_internalConnection.ddm = registry->createDataDeviceManager(ddmInterface.name, ddmInterface.version, this); + } } ); registry->setup(); } ); m_internalConnection.client->initConnection(); } void WaylandServer::removeClient(ShellClient *c) { m_clients.removeAll(c); m_internalClients.removeAll(c); emit shellClientRemoved(c); } void WaylandServer::dispatch() { if (!m_display) { return; } if (m_internalConnection.server) { m_internalConnection.server->flush(); } m_display->dispatchEvents(0); } static ShellClient *findClientInList(const QList &clients, quint32 id) { auto it = std::find_if(clients.begin(), clients.end(), [id] (ShellClient *c) { return c->windowId() == id; } ); if (it == clients.end()) { return nullptr; } return *it; } static ShellClient *findClientInList(const QList &clients, KWayland::Server::SurfaceInterface *surface) { auto it = std::find_if(clients.begin(), clients.end(), [surface] (ShellClient *c) { return c->surface() == surface; } ); if (it == clients.end()) { return nullptr; } return *it; } ShellClient *WaylandServer::findClient(quint32 id) const { if (id == 0) { return nullptr; } if (ShellClient *c = findClientInList(m_clients, id)) { return c; } if (ShellClient *c = findClientInList(m_internalClients, id)) { return c; } return nullptr; } ShellClient *WaylandServer::findClient(SurfaceInterface *surface) const { if (!surface) { return nullptr; } if (ShellClient *c = findClientInList(m_clients, surface)) { return c; } if (ShellClient *c = findClientInList(m_internalClients, surface)) { return c; } return nullptr; } AbstractClient *WaylandServer::findAbstractClient(SurfaceInterface *surface) const { return findClient(surface); } ShellClient *WaylandServer::findClient(QWindow *w) const { if (!w) { return nullptr; } auto it = std::find_if(m_internalClients.constBegin(), m_internalClients.constEnd(), [w] (const ShellClient *c) { return c->internalWindow() == w; } ); if (it != m_internalClients.constEnd()) { return *it; } return nullptr; } quint32 WaylandServer::createWindowId(SurfaceInterface *surface) { auto it = m_clientIds.constFind(surface->client()); quint16 clientId = 0; if (it != m_clientIds.constEnd()) { clientId = it.value(); } else { clientId = createClientId(surface->client()); } Q_ASSERT(clientId != 0); quint32 id = clientId; // TODO: this does not prevent that two surfaces of same client get same id id = (id << 16) | (surface->id() & 0xFFFF); if (findClient(id)) { qCWarning(KWIN_CORE) << "Invalid client windowId generated:" << id; return 0; } return id; } quint16 WaylandServer::createClientId(ClientConnection *c) { auto ids = m_clientIds.values().toSet(); quint16 id = 1; if (!ids.isEmpty()) { for (quint16 i = ids.count() + 1; i >= 1 ; i--) { if (!ids.contains(i)) { id = i; break; } } } Q_ASSERT(!ids.contains(id)); m_clientIds.insert(c, id); connect(c, &ClientConnection::disconnected, this, [this] (ClientConnection *c) { m_clientIds.remove(c); } ); return id; } bool WaylandServer::isScreenLocked() const { if (!hasScreenLockerIntegration()) { return false; } return ScreenLocker::KSldApp::self()->lockState() == ScreenLocker::KSldApp::Locked || ScreenLocker::KSldApp::self()->lockState() == ScreenLocker::KSldApp::AcquiringLock; } bool WaylandServer::hasScreenLockerIntegration() const { return !m_initFlags.testFlag(InitalizationFlag::NoLockScreenIntegration); } bool WaylandServer::hasGlobalShortcutSupport() const { return !m_initFlags.testFlag(InitalizationFlag::NoGlobalShortcuts); } void WaylandServer::simulateUserActivity() { if (m_idle) { m_idle->simulateUserActivity(); } } } diff --git a/wayland_server.h b/wayland_server.h index 696955a8a..2941ee18a 100644 --- a/wayland_server.h +++ b/wayland_server.h @@ -1,287 +1,302 @@ /******************************************************************** 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 . *********************************************************************/ #ifndef KWIN_WAYLAND_SERVER_H #define KWIN_WAYLAND_SERVER_H #include #include #include class QThread; class QProcess; class QWindow; namespace KWayland { namespace Client { class ConnectionThread; class Registry; +class Seat; +class DataDeviceManager; class ShmPool; class Surface; } namespace Server { class AppMenuManagerInterface; class ClientConnection; class CompositorInterface; class Display; class DataDeviceInterface; class IdleInterface; class ShellInterface; class SeatInterface; +class DataDeviceManagerInterface; class ServerSideDecorationManagerInterface; class ServerSideDecorationPaletteManagerInterface; class SurfaceInterface; class OutputInterface; class PlasmaShellInterface; class PlasmaShellSurfaceInterface; class PlasmaVirtualDesktopManagementInterface; class PlasmaWindowManagementInterface; class QtSurfaceExtensionInterface; class OutputManagementInterface; class OutputConfigurationInterface; class XdgDecorationManagerInterface; class XdgShellInterface; class XdgForeignInterface; class XdgOutputManagerInterface; } } namespace KWin { class ShellClient; class AbstractClient; class Toplevel; class KWIN_EXPORT WaylandServer : public QObject { Q_OBJECT public: enum class InitalizationFlag { NoOptions = 0x0, LockScreen = 0x1, NoLockScreenIntegration = 0x2, NoGlobalShortcuts = 0x4 }; Q_DECLARE_FLAGS(InitalizationFlags, InitalizationFlag) virtual ~WaylandServer(); bool init(const QByteArray &socketName = QByteArray(), InitalizationFlags flags = InitalizationFlag::NoOptions); void terminateClientConnections(); KWayland::Server::Display *display() { return m_display; } KWayland::Server::CompositorInterface *compositor() { return m_compositor; } KWayland::Server::SeatInterface *seat() { return m_seat; } + KWayland::Server::DataDeviceManagerInterface *dataDeviceManager() { + return m_dataDeviceManager; + } KWayland::Server::ShellInterface *shell() { return m_shell; } KWayland::Server::PlasmaVirtualDesktopManagementInterface *virtualDesktopManagement() { return m_virtualDesktopManagement; } KWayland::Server::PlasmaWindowManagementInterface *windowManagement() { return m_windowManagement; } KWayland::Server::ServerSideDecorationManagerInterface *decorationManager() const { return m_decorationManager; } KWayland::Server::XdgOutputManagerInterface *xdgOutputManager() const { return m_xdgOutputManager; } QList clients() const { return m_clients; } QList internalClients() const { return m_internalClients; } void removeClient(ShellClient *c); ShellClient *findClient(quint32 id) const; ShellClient *findClient(KWayland::Server::SurfaceInterface *surface) const; AbstractClient *findAbstractClient(KWayland::Server::SurfaceInterface *surface) const; ShellClient *findClient(QWindow *w) const; /** * @returns a transient parent of a surface imported with the foreign protocol, if any **/ KWayland::Server::SurfaceInterface *findForeignTransientForSurface(KWayland::Server::SurfaceInterface *surface); /** * @returns file descriptor for Xwayland to connect to. **/ int createXWaylandConnection(); void destroyXWaylandConnection(); /** * @returns file descriptor to the input method server's socket. **/ int createInputMethodConnection(); void destroyInputMethodConnection(); int createXclipboardSyncConnection(); /** * @returns true if screen is locked. **/ bool isScreenLocked() const; /** * @returns whether integration with KScreenLocker is available. **/ bool hasScreenLockerIntegration() const; /** * @returns whether any kind of global shortcuts are supported. **/ bool hasGlobalShortcutSupport() const; void createInternalConnection(); void initWorkspace(); KWayland::Server::ClientConnection *xWaylandConnection() const { return m_xwayland.client; } KWayland::Server::ClientConnection *inputMethodConnection() const { return m_inputMethodServerConnection; } KWayland::Server::ClientConnection *internalConnection() const { return m_internalConnection.server; } KWayland::Server::ClientConnection *screenLockerClientConnection() const { return m_screenLockerClientConnection; } QPointer xclipboardSyncDataDevice() const { return m_xclipbaordSync.ddi; } + KWayland::Client::Seat *internalSeat() { + return m_internalConnection.seat; + } + KWayland::Client::DataDeviceManager *internalDataDeviceManager() { + return m_internalConnection.ddm; + } KWayland::Client::ShmPool *internalShmPool() { return m_internalConnection.shm; } KWayland::Client::ConnectionThread *internalClientConection() { return m_internalConnection.client; } KWayland::Client::Registry *internalClientRegistry() { return m_internalConnection.registry; } void dispatch(); quint32 createWindowId(KWayland::Server::SurfaceInterface *surface); /** * Struct containing information for a created Wayland connection through a * socketpair. **/ struct SocketPairConnection { /** * ServerSide Connection **/ KWayland::Server::ClientConnection *connection = nullptr; /** * client-side file descriptor for the socket **/ int fd = -1; }; /** * Creates a Wayland connection using a socket pair. **/ SocketPairConnection createConnection(); void simulateUserActivity(); Q_SIGNALS: void shellClientAdded(KWin::ShellClient*); void shellClientRemoved(KWin::ShellClient*); void terminatingInternalClientConnection(); void initialized(); void foreignTransientChanged(KWayland::Server::SurfaceInterface *child); void xclipboardSyncDataDeviceCreated(); private: void setupX11ClipboardSync(); void shellClientShown(Toplevel *t); void initOutputs(); void syncOutputsToWayland(); quint16 createClientId(KWayland::Server::ClientConnection *c); void destroyInternalConnection(); void configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config); template void createSurface(T *surface); void initScreenLocker(); KWayland::Server::Display *m_display = nullptr; KWayland::Server::CompositorInterface *m_compositor = nullptr; KWayland::Server::SeatInterface *m_seat = nullptr; + KWayland::Server::DataDeviceManagerInterface *m_dataDeviceManager = nullptr; KWayland::Server::ShellInterface *m_shell = nullptr; KWayland::Server::XdgShellInterface *m_xdgShell5 = nullptr; KWayland::Server::XdgShellInterface *m_xdgShell6 = nullptr; KWayland::Server::XdgShellInterface *m_xdgShell = nullptr; KWayland::Server::PlasmaShellInterface *m_plasmaShell = nullptr; KWayland::Server::PlasmaWindowManagementInterface *m_windowManagement = nullptr; KWayland::Server::PlasmaVirtualDesktopManagementInterface *m_virtualDesktopManagement = nullptr; KWayland::Server::ServerSideDecorationManagerInterface *m_decorationManager = nullptr; KWayland::Server::OutputManagementInterface *m_outputManagement = nullptr; KWayland::Server::AppMenuManagerInterface *m_appMenuManager = nullptr; KWayland::Server::ServerSideDecorationPaletteManagerInterface *m_paletteManager = nullptr; KWayland::Server::IdleInterface *m_idle = nullptr; KWayland::Server::XdgOutputManagerInterface *m_xdgOutputManager = nullptr; KWayland::Server::XdgDecorationManagerInterface *m_xdgDecorationManager = nullptr; struct { KWayland::Server::ClientConnection *client = nullptr; QMetaObject::Connection destroyConnection; } m_xwayland; KWayland::Server::ClientConnection *m_inputMethodServerConnection = nullptr; KWayland::Server::ClientConnection *m_screenLockerClientConnection = nullptr; struct { KWayland::Server::ClientConnection *server = nullptr; KWayland::Client::ConnectionThread *client = nullptr; QThread *clientThread = nullptr; KWayland::Client::Registry *registry = nullptr; + KWayland::Client::Seat *seat = nullptr; + KWayland::Client::DataDeviceManager *ddm = nullptr; KWayland::Client::ShmPool *shm = nullptr; bool interfacesAnnounced = false; } m_internalConnection; struct { QProcess *process = nullptr; KWayland::Server::ClientConnection *client = nullptr; QPointer ddi; } m_xclipbaordSync; KWayland::Server::XdgForeignInterface *m_XdgForeign = nullptr; QList m_clients; QList m_internalClients; QHash m_clientIds; InitalizationFlags m_initFlags; QVector m_plasmaShellSurfaces; KWIN_SINGLETON(WaylandServer) }; inline WaylandServer *waylandServer() { return WaylandServer::self(); } } // namespace KWin #endif diff --git a/xwl/clipboard.cpp b/xwl/clipboard.cpp new file mode 100644 index 000000000..b33f5400c --- /dev/null +++ b/xwl/clipboard.cpp @@ -0,0 +1,179 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright 2019 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "clipboard.h" + +#include "xwayland.h" +#include "databridge.h" +#include "selection_source.h" +#include "transfer.h" + +#include "wayland_server.h" +#include "workspace.h" +#include "abstract_client.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +namespace KWin { +namespace Xwl { + +Clipboard::Clipboard(xcb_atom_t atom, QObject *parent) + : Selection(atom, parent) +{ + auto *xcbConn = kwinApp()->x11Connection(); + + const uint32_t clipboardValues[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | + XCB_EVENT_MASK_PROPERTY_CHANGE }; + xcb_create_window(xcbConn, + XCB_COPY_FROM_PARENT, + window(), + kwinApp()->x11RootWindow(), + 0, 0, + 10, 10, + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + Xwayland::self()->xcbScreen()->root_visual, + XCB_CW_EVENT_MASK, + clipboardValues); + registerXfixes(); + xcb_flush(xcbConn); + + connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::selectionChanged, + this, &Clipboard::wlSelectionChanged); +} + +void Clipboard::wlSelectionChanged(KWayland::Server::DataDeviceInterface *ddi) +{ + if (ddi && ddi != DataBridge::self()->dataDeviceIface()) { + // Wayland native client provides new selection + if (!m_checkConnection) { + m_checkConnection = connect(workspace(), &Workspace::clientActivated, + this, [this](AbstractClient *ac) { + Q_UNUSED(ac); + checkWlSource(); + }); + } + // remove previous source so checkWlSource() can create a new one + setWlSource(nullptr); + } + checkWlSource(); +} + +void Clipboard::checkWlSource() +{ + auto ddi = waylandServer()->seat()->selection(); + auto removeSource = [this] { + if (wlSource()) { + setWlSource(nullptr); + ownSelection(false); + } + }; + + // Wayland source gets created when: + // - the Wl selection exists, + // - its source is not Xwayland, + // - a client is active, + // - this client is an Xwayland one. + // + // Otherwise the Wayland source gets destroyed to shield + // against snooping X clients. + + if (!ddi || DataBridge::self()->dataDeviceIface() == ddi) { + // Xwayland source or no source + disconnect(m_checkConnection); + m_checkConnection = QMetaObject::Connection(); + removeSource(); + return; + } + if (!workspace()->activeClient() || !workspace()->activeClient()->inherits("KWin::Client")) { + // no active client or active client is Wayland native + removeSource(); + return; + } + // Xwayland client is active and we need a Wayland source + if (wlSource()) { + // source already exists, nothing more to do + return; + } + auto *wls = new WlSource(this, ddi); + setWlSource(wls); + auto *dsi = ddi->selection(); + if (dsi) { + wls->setDataSourceIface(dsi); + } + connect(ddi, &KWayland::Server::DataDeviceInterface::selectionChanged, + wls, &WlSource::setDataSourceIface); + ownSelection(true); +} + +void Clipboard::doHandleXfixesNotify(xcb_xfixes_selection_notify_event_t *event) +{ + createX11Source(event); + auto *xSrc = x11Source(); + if (xSrc) { + xSrc->getTargets(); + } +} + +void Clipboard::x11OffersChanged(const QVector &added, const QVector &removed) +{ + auto *xSrc = x11Source(); + if (!xSrc) { + return; + } + + const auto offers = xSrc->offers(); + const bool hasOffers = offers.size() > 0; + + if (hasOffers) { + if (!xSrc->dataSource() || !removed.isEmpty()) { + // create new Wl DataSource if there is none or when types + // were removed (Wl Data Sources can only add types) + auto *ddm = waylandServer()->internalDataDeviceManager(); + auto *ds = ddm->createDataSource(xSrc); + + // also offers directly the currently available types + xSrc->setDataSource(ds); + DataBridge::self()->dataDevice()->setSelection(0, ds); + waylandServer()->seat()->setSelection(DataBridge::self()->dataDeviceIface()); + } else if (auto *ds = xSrc->dataSource()) { + for (const auto &mime : added) { + ds->offer(mime); + } + } + } else { + waylandServer()->seat()->setSelection(nullptr); + } + waylandServer()->internalClientConection()->flush(); + waylandServer()->dispatch(); +} + +} +} diff --git a/xwl/xwayland.h b/xwl/clipboard.h similarity index 53% copy from xwl/xwayland.h copy to xwl/clipboard.h index c07c6a1b5..80d444bf8 100644 --- a/xwl/xwayland.h +++ b/xwl/clipboard.h @@ -1,62 +1,67 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ -#ifndef KWIN_XWL_XWAYLAND -#define KWIN_XWL_XWAYLAND +#ifndef KWIN_XWL_CLIPBOARD +#define KWIN_XWL_CLIPBOARD -#include +#include "selection.h" -class QProcess; - -class xcb_screen_t; +namespace KWayland +{ +namespace Server +{ +class DataDeviceInterface; +} +} namespace KWin { -class ApplicationWaylandAbstract; - namespace Xwl { -class Xwayland : public QObject +/** + * Represents the X clipboard, which is on Wayland side just called + * @e selection. + */ +class Clipboard : public Selection { Q_OBJECT public: - Xwayland(ApplicationWaylandAbstract *app, QObject *parent = nullptr); - virtual ~Xwayland(); - - void init(); - -Q_SIGNALS: - void criticalError(int code); + Clipboard(xcb_atom_t atom, QObject *parent); private: - void createX11Connection(); - void continueStartupWithX(); - - int m_xcbConnectionFd = -1; - QProcess *m_xwaylandProcess = nullptr; - QMetaObject::Connection m_xwaylandFailConnection; - - ApplicationWaylandAbstract *m_app; + void doHandleXfixesNotify(xcb_xfixes_selection_notify_event_t *event) override; + void x11OffersChanged(const QVector &added, const QVector &removed) override; + /** + * React to Wl selection change. + */ + void wlSelectionChanged(KWayland::Server::DataDeviceInterface *ddi); + /** + * Check the current state of the selection and if a source needs + * to be created or destroyed. + */ + void checkWlSource(); + + QMetaObject::Connection m_checkConnection; }; } } #endif diff --git a/xwl/databridge.cpp b/xwl/databridge.cpp new file mode 100644 index 000000000..38433f93f --- /dev/null +++ b/xwl/databridge.cpp @@ -0,0 +1,106 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright 2018 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "databridge.h" +#include "xwayland.h" +#include "selection.h" +#include "clipboard.h" + +#include "atoms.h" +#include "wayland_server.h" +#include "workspace.h" +#include "abstract_client.h" + +// KWayland +#include +#include + +#include +#include +#include + +namespace KWin { +namespace Xwl { + +static DataBridge *s_self = nullptr; +DataBridge* DataBridge::self() +{ + return s_self; +} + +DataBridge::DataBridge(QObject *parent) + : QObject(parent) +{ + s_self = this; + auto *ddm = waylandServer()->internalDataDeviceManager(); + auto *seat = waylandServer()->internalSeat(); + m_dd = ddm->getDataDevice(seat, this); + waylandServer()->dispatch(); + + const auto *ddmi = waylandServer()->dataDeviceManager(); + auto *dc = new QMetaObject::Connection(); + *dc = connect(ddmi, &KWayland::Server::DataDeviceManagerInterface::dataDeviceCreated, this, + [this, dc](KWayland::Server::DataDeviceInterface *ddi) { + if (m_ddi || ddi->client() != waylandServer()->internalConnection()) { + return; + } + QObject::disconnect(*dc); + delete dc; + m_ddi = ddi; + init(); + } + ); +} + +DataBridge::~DataBridge() +{ + s_self = nullptr; +} + +void DataBridge::init() +{ + m_clipboard = new Clipboard(atoms->clipboard, this); + waylandServer()->dispatch(); +} + +bool DataBridge::filterEvent(xcb_generic_event_t *event) +{ + if (m_clipboard->filterEvent(event)) { + return true; + } + if (event->response_type - Xwayland::self()->xfixes()->first_event == XCB_XFIXES_SELECTION_NOTIFY) { + return handleXfixesNotify((xcb_xfixes_selection_notify_event_t *)event); + } + return false; +} + +bool DataBridge::handleXfixesNotify(xcb_xfixes_selection_notify_event_t *event) +{ + auto getSelection = [this](xcb_atom_t atom) -> Selection* { + if (atom == atoms->clipboard) { + return m_clipboard; + } + return nullptr; + }; + auto *sel = getSelection(event->selection); + return sel && sel->handleXfixesNotify(event); +} + +} +} diff --git a/xwl/databridge.h b/xwl/databridge.h new file mode 100644 index 000000000..ec8109ed3 --- /dev/null +++ b/xwl/databridge.h @@ -0,0 +1,86 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright 2018 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef KWIN_XWL_DATABRIDGE +#define KWIN_XWL_DATABRIDGE + +#include + +#include + +class xcb_xfixes_selection_notify_event_t; + +namespace KWayland { +namespace Client { +class DataDevice; +} +namespace Server { +class DataDeviceInterface; +class SurfaceInterface; +} +} + +namespace KWin +{ + +namespace Xwl +{ +class Xwayland; +class Clipboard; + +/* + * Interface class for all data sharing in the context of X selections + * and Wayland's internal mechanism. + * + * Exists only once per Xwayland session. + */ +class DataBridge : public QObject +{ + Q_OBJECT +public: + static DataBridge* self(); + + explicit DataBridge(QObject *parent = nullptr); + ~DataBridge(); + + bool filterEvent(xcb_generic_event_t *event); + + KWayland::Client::DataDevice *dataDevice() const { + return m_dd; + } + KWayland::Server::DataDeviceInterface *dataDeviceIface() const { + return m_ddi; + } + +private: + void init(); + + bool handleXfixesNotify(xcb_xfixes_selection_notify_event_t *event); + + Clipboard *m_clipboard = nullptr; + + /* Internal data device interface */ + KWayland::Client::DataDevice *m_dd = nullptr; + KWayland::Server::DataDeviceInterface *m_ddi = nullptr; +}; + +} +} + +#endif diff --git a/xwl/selection.cpp b/xwl/selection.cpp new file mode 100644 index 000000000..dca9b3d13 --- /dev/null +++ b/xwl/selection.cpp @@ -0,0 +1,358 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright 2019 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "selection.h" +#include "databridge.h" +#include "selection_source.h" +#include "transfer.h" + +#include "atoms.h" +#include "workspace.h" +#include "client.h" + +#include +#include + +#include + +namespace KWin { +namespace Xwl { + +xcb_atom_t Selection::mimeTypeToAtom(const QString &mimeType) +{ + if (mimeType == QLatin1String("text/plain;charset=utf-8")) { + return atoms->utf8_string; + } else if (mimeType == QLatin1String("text/plain")) { + return atoms->text; + } else if (mimeType == QLatin1String("text/x-uri")) { + return atoms->uri_list; + } + return mimeTypeToAtomLiteral(mimeType); +} + +xcb_atom_t Selection::mimeTypeToAtomLiteral(const QString &mimeType) +{ + return Xcb::Atom(mimeType.toLatin1(), false, kwinApp()->x11Connection()); +} + +QStringList Selection::atomToMimeTypes(xcb_atom_t atom) +{ + QStringList mimeTypes; + + if (atom == atoms->utf8_string) { + mimeTypes << QString::fromLatin1("text/plain;charset=utf-8"); + } else if (atom == atoms->text) { + mimeTypes << QString::fromLatin1("text/plain"); + } else if (atom == atoms->uri_list) { + mimeTypes << "text/uri-list" << "text/x-uri"; + } else { + auto *xcbConn = kwinApp()->x11Connection(); + xcb_get_atom_name_cookie_t nameCookie = xcb_get_atom_name(xcbConn, atom); + xcb_get_atom_name_reply_t *nameReply = xcb_get_atom_name_reply(xcbConn, nameCookie, NULL); + if (nameReply == NULL) { + return QStringList(); + } + + size_t len = xcb_get_atom_name_name_length(nameReply); + char *name = xcb_get_atom_name_name(nameReply); + mimeTypes << QString::fromLatin1(name, len); + free(nameReply); + } + return mimeTypes; +} + +Selection::Selection(xcb_atom_t atom, QObject *parent) + : QObject(parent), + m_atom(atom) +{ + auto *xcbConn = kwinApp()->x11Connection(); + m_window = xcb_generate_id(kwinApp()->x11Connection()); + xcb_flush(xcbConn); +} + +bool Selection::handleXfixesNotify(xcb_xfixes_selection_notify_event_t *event) +{ + if (event->window != m_window) { + return false; + } + if (event->selection != m_atom) { + return false; + } + if (m_disownPending) { + // notify of our own disown - ignore it + m_disownPending = false; + return true; + } + if (event->owner == m_window && m_wlSrc) { + // When we claim a selection we must use XCB_TIME_CURRENT, + // grab the actual timestamp here to answer TIMESTAMP requests + // correctly + m_wlSrc->setTimestamp(event->timestamp); + m_timestamp = event->timestamp; + return true; + } + + // Being here means some other X window has claimed the selection. + delete m_xSrc; + m_xSrc = nullptr; + const auto *ac = workspace()->activeClient(); + if (!ac || !ac->inherits("KWin::Client")) { + // selections are only allowed to be acquired when Xwayland has focus + // TODO: can we make this stronger (window id comparision)? + return true; + } + doHandleXfixesNotify(event); + return true; +} + +bool Selection::filterEvent(xcb_generic_event_t *event) +{ + switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) { + case XCB_SELECTION_NOTIFY: + if (handleSelNotify(reinterpret_cast(event))) { + return true; + } + Q_FALLTHROUGH(); + case XCB_PROPERTY_NOTIFY: + if (handlePropNotify(reinterpret_cast(event))) { + return true; + } + Q_FALLTHROUGH(); + case XCB_SELECTION_REQUEST: + if (handleSelRequest(reinterpret_cast(event))) { + return true; + } + Q_FALLTHROUGH(); + case XCB_CLIENT_MESSAGE: + if (handleClientMessage(reinterpret_cast(event))) { + return true; + } + Q_FALLTHROUGH(); + default: + return false; + } +} + +void Selection::sendSelNotify(xcb_selection_request_event_t *event, bool success) +{ + xcb_selection_notify_event_t notify; + notify.response_type = XCB_SELECTION_NOTIFY; + notify.sequence = 0; + notify.time = event->time; + notify.requestor = event->requestor; + notify.selection = event->selection; + notify.target = event->target; + notify.property = success ? event->property : (xcb_atom_t)XCB_ATOM_NONE; + + auto *xcbConn = kwinApp()->x11Connection(); + xcb_send_event(xcbConn, + 0, + event->requestor, + XCB_EVENT_MASK_NO_EVENT, + (const char *)¬ify); + xcb_flush(xcbConn); +} + +void Selection::registerXfixes() +{ + auto *xcbConn = kwinApp()->x11Connection(); + const uint32_t mask = XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER | + XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY | + XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE; + xcb_xfixes_select_selection_input(kwinApp()->x11Connection(), + m_window, + m_atom, + mask); + xcb_flush(xcbConn); +} + +void Selection::setWlSource(WlSource *src) +{ + delete m_wlSrc; + delete m_xSrc; + m_wlSrc = nullptr; + m_xSrc = nullptr; + if (src) { + m_wlSrc = src; + connect(src, &WlSource::transferReady, this, &Selection::startTransferToX); + } +} + +void Selection::createX11Source(xcb_xfixes_selection_notify_event_t *event) +{ + delete m_wlSrc; + delete m_xSrc; + m_wlSrc = nullptr; + m_xSrc = nullptr; + if (event->owner == XCB_WINDOW_NONE) { + return; + } + m_xSrc = new X11Source(this, event); + + connect(m_xSrc, &X11Source::offersChanged, this, &Selection::x11OffersChanged); + connect(m_xSrc, &X11Source::transferReady, this, &Selection::startTransferToWayland); +} + +void Selection::ownSelection(bool own) +{ + auto *xcbConn = kwinApp()->x11Connection(); + if (own) { + xcb_set_selection_owner(xcbConn, + m_window, + m_atom, + XCB_TIME_CURRENT_TIME); + } else { + m_disownPending = true; + xcb_set_selection_owner(xcbConn, + XCB_WINDOW_NONE, + m_atom, + m_timestamp); + } + xcb_flush(xcbConn); +} + +bool Selection::handleSelRequest(xcb_selection_request_event_t *event) +{ + if (event->selection != m_atom) { + return false; + } + + if (qobject_cast(workspace()->activeClient()) == nullptr) { + // Receiving Wayland selection not allowed when no Xwayland surface active + // filter the event, but don't act upon it + sendSelNotify(event, false); + return true; + } + + if (m_window != event->owner || !m_wlSrc) { + if (event->time < m_timestamp) { + // cancel earlier attempts at receiving a selection + // TODO: is this for sure without problems? + sendSelNotify(event, false); + return true; + } + return false; + } + return m_wlSrc->handleSelRequest(event); +} + +bool Selection::handleSelNotify(xcb_selection_notify_event_t *event) +{ + if (m_xSrc && m_xSrc->handleSelNotify(event)) { + return true; + } + for (auto *transfer : m_xToWlTransfers) { + if (transfer->handleSelNotify(event)) { + return true; + } + } + return false; +} + +bool Selection::handlePropNotify(xcb_property_notify_event_t *event) +{ + for (auto *transfer : m_xToWlTransfers) { + if (transfer->handlePropNotify(event)) { + return true; + } + } + for (auto *transfer : m_wlToXTransfers) { + if (transfer->handlePropNotify(event)) { + return true; + } + } + return false; +} + +void Selection::startTransferToWayland(xcb_atom_t target, qint32 fd) +{ + // create new x to wl data transfer object + auto *transfer = new TransferXtoWl(m_atom, target, fd, m_xSrc->timestamp(), m_window, this); + m_xToWlTransfers << transfer; + + connect(transfer, &TransferXtoWl::finished, this, [this, transfer]() { + Q_EMIT transferFinished(transfer->timestamp()); + delete transfer; + m_xToWlTransfers.removeOne(transfer); + endTimeoutTransfersTimer(); + }); + startTimeoutTransfersTimer(); +} + +void Selection::startTransferToX(xcb_selection_request_event_t *event, qint32 fd) +{ + // create new wl to x data transfer object + auto *transfer = new TransferWltoX(m_atom, event, fd, this); + + connect(transfer, &TransferWltoX::selNotify, this, &Selection::sendSelNotify); + connect(transfer, &TransferWltoX::finished, this, [this, transfer]() { + Q_EMIT transferFinished(transfer->timestamp()); + + // TODO: serialize? see comment below. +// const bool wasActive = (transfer == m_wlToXTransfers[0]); + delete transfer; + m_wlToXTransfers.removeOne(transfer); + endTimeoutTransfersTimer(); +// if (wasActive && !m_wlToXTransfers.isEmpty()) { +// m_wlToXTransfers[0]->startTransferFromSource(); +// } + }); + + // add it to list of queued transfers + m_wlToXTransfers.append(transfer); + + // TODO: Do we need to serialize the transfers, or can we do + // them in parallel as we do it right now? + transfer->startTransferFromSource(); +// if (m_wlToXTransfers.size() == 1) { +// transfer->startTransferFromSource(); +// } + startTimeoutTransfersTimer(); +} + +void Selection::startTimeoutTransfersTimer() +{ + if (m_timeoutTransfers) { + return; + } + m_timeoutTransfers = new QTimer(this); + connect(m_timeoutTransfers, &QTimer::timeout, this, &Selection::timeoutTransfers); + m_timeoutTransfers->start(5000); +} + +void Selection::endTimeoutTransfersTimer() +{ + if (m_xToWlTransfers.isEmpty() && m_wlToXTransfers.isEmpty()) { + delete m_timeoutTransfers; + m_timeoutTransfers = nullptr; + } +} + +void Selection::timeoutTransfers() +{ + for (auto *transfer : m_xToWlTransfers) { + transfer->timeout(); + } + for (auto *transfer : m_wlToXTransfers) { + transfer->timeout(); + } +} + +} +} diff --git a/xwl/selection.h b/xwl/selection.h new file mode 100644 index 000000000..45bd05bac --- /dev/null +++ b/xwl/selection.h @@ -0,0 +1,138 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright 2019 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef KWIN_XWL_SELECTION +#define KWIN_XWL_SELECTION + +#include +#include + +#include + +struct xcb_xfixes_selection_notify_event_t; + +class QTimer; + +namespace KWin +{ +namespace Xwl +{ +class TransferWltoX; +class TransferXtoWl; +class WlSource; +class X11Source; + +/* + * Base class representing generic X selections and their respective + * Wayland counter-parts. + * + * The class needs to be subclassed and adjusted according to the + * selection, but provides common fucntionality to be expected of all + * selections. + * + * A selection should exist through the whole runtime of an Xwayland + * session. + * + * Independently of each other the class holds the currently active + * source instance and active transfers relative to the represented + * selection. + */ +class Selection : public QObject +{ + Q_OBJECT +public: + static xcb_atom_t mimeTypeToAtom(const QString &mimeType); + static xcb_atom_t mimeTypeToAtomLiteral(const QString &mimeType); + static QStringList atomToMimeTypes(xcb_atom_t atom); + + // on selection owner changes by X clients (Xwl -> Wl) + bool handleXfixesNotify(xcb_xfixes_selection_notify_event_t *event); + bool filterEvent(xcb_generic_event_t *event); + void sendSelNotify(xcb_selection_request_event_t *event, bool success); + + xcb_atom_t atom() const { + return m_atom; + } + xcb_window_t window() const { + return m_window; + } + +Q_SIGNALS: + void transferFinished(xcb_timestamp_t eventTime); + +protected: + Selection(xcb_atom_t atom, QObject *parent); + void registerXfixes(); + + virtual void doHandleXfixesNotify(xcb_xfixes_selection_notify_event_t *event) = 0; + virtual void x11OffersChanged(const QVector &added, const QVector &removed) = 0; + + virtual bool handleClientMessage(xcb_client_message_event_t *event) { + Q_UNUSED(event); + return false; + } + // sets the current provider of the selection + void setWlSource(WlSource *src); + WlSource* wlSource() const { + return m_wlSrc; + } + void createX11Source(xcb_xfixes_selection_notify_event_t *event); + X11Source* x11Source() const { + return m_xSrc; + } + // must be called in order to provide data from Wl to X + void ownSelection(bool own); + void setWindow(xcb_window_t window) { + m_window = window; + } + +private: + bool handleSelRequest(xcb_selection_request_event_t *event); + bool handleSelNotify(xcb_selection_notify_event_t *event); + bool handlePropNotify(xcb_property_notify_event_t *event); + + void startTransferToWayland(xcb_atom_t target, qint32 fd); + void startTransferToX(xcb_selection_request_event_t *event, qint32 fd); + + // Timeout transfers, which have become inactive due to client errors. + void timeoutTransfers(); + void startTimeoutTransfersTimer(); + void endTimeoutTransfersTimer(); + + xcb_atom_t m_atom = XCB_ATOM_NONE; + xcb_window_t m_window = XCB_WINDOW_NONE; + xcb_timestamp_t m_timestamp; + + // Active source, if any. Only one of them at max can exist + // at the same time. + WlSource *m_wlSrc = nullptr; + X11Source *m_xSrc = nullptr; + + // active transfers + QVector m_wlToXTransfers; + QVector m_xToWlTransfers; + QTimer *m_timeoutTransfers = nullptr; + + bool m_disownPending = false; +}; + +} +} + +#endif diff --git a/xwl/selection_source.cpp b/xwl/selection_source.cpp new file mode 100644 index 000000000..513171be8 --- /dev/null +++ b/xwl/selection_source.cpp @@ -0,0 +1,314 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright 2019 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "selection_source.h" +#include "selection.h" +#include "transfer.h" + +#include "atoms.h" +#include "wayland_server.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +namespace KWin { +namespace Xwl { + +SelectionSource::SelectionSource(Selection *sel) + : QObject(sel), + m_sel(sel) +{ +} + +WlSource::WlSource(Selection *sel, KWayland::Server::DataDeviceInterface *ddi) + : SelectionSource(sel), + m_ddi(ddi) +{ + Q_ASSERT(ddi); +} + +void WlSource::setDataSourceIface(KWayland::Server::DataSourceInterface *dsi) +{ + if (m_dsi == dsi) { + return; + } + for (const auto &mime : dsi->mimeTypes()) { + m_offers << mime; + } + m_offerCon = connect(dsi, + &KWayland::Server::DataSourceInterface::mimeTypeOffered, + this, &WlSource::receiveOffer); + m_dsi = dsi; +} + +void WlSource::receiveOffer(const QString &mime) +{ + m_offers << mime; +} + +void WlSource::sendSelNotify(xcb_selection_request_event_t *event, bool success) +{ + selection()->sendSelNotify(event, success); +} + +bool WlSource::handleSelRequest(xcb_selection_request_event_t *event) +{ + if (event->target == atoms->targets) { + sendTargets(event); + } else if (event->target == atoms->timestamp) { + sendTimestamp(event); + } else if (event->target == atoms->delete_atom) { + sendSelNotify(event, true); + } else { + // try to send mime data + if (!checkStartTransfer(event)) { + sendSelNotify(event, false); + } + } + return true; +} + +void WlSource::sendTargets(xcb_selection_request_event_t *event) +{ + QVector targets; + targets.resize(m_offers.size() + 2); + targets[0] = atoms->timestamp; + targets[1] = atoms->targets; + + size_t cnt = 2; + for (const auto mime : m_offers) { + targets[cnt] = Selection::mimeTypeToAtom(mime); + cnt++; + } + + xcb_change_property(kwinApp()->x11Connection(), + XCB_PROP_MODE_REPLACE, + event->requestor, + event->property, + XCB_ATOM_ATOM, + 32, cnt, targets.data()); + sendSelNotify(event, true); +} + +void WlSource::sendTimestamp(xcb_selection_request_event_t *event) +{ + const xcb_timestamp_t time = timestamp(); + xcb_change_property(kwinApp()->x11Connection(), + XCB_PROP_MODE_REPLACE, + event->requestor, + event->property, + XCB_ATOM_INTEGER, + 32, 1, &time); + + sendSelNotify(event, true); +} + +bool WlSource::checkStartTransfer(xcb_selection_request_event_t *event) +{ + // check interfaces available + if (!m_ddi || !m_dsi) { + return false; + } + + const auto targets = Selection::atomToMimeTypes(event->target); + if (targets.isEmpty()) { + qCDebug(KWIN_XWL) << "Unknown selection atom. Ignoring request."; + return false; + } + const auto firstTarget = targets[0]; + + auto cmp = [firstTarget](const QString &b) { + if (firstTarget == "text/uri-list") { + // Wayland sources might announce the old mime or the new standard + return firstTarget == b || b == "text/x-uri"; + } + return firstTarget == b; + }; + // check supported mimes + const auto offers = m_dsi->mimeTypes(); + const auto mimeIt = std::find_if(offers.begin(), offers.end(), cmp); + if (mimeIt == offers.end()) { + // Requested Mime not supported. Not sending selection. + return false; + } + + int p[2]; + if (pipe(p) == -1) { + qCWarning(KWIN_XWL) << "Pipe failed. Not sending selection."; + return false; + } + + m_dsi->requestData(*mimeIt, p[1]); + waylandServer()->dispatch(); + + Q_EMIT transferReady(new xcb_selection_request_event_t(*event), p[0]); + return true; +} + +X11Source::X11Source(Selection *sel, xcb_xfixes_selection_notify_event_t *event) + : SelectionSource(sel), + m_owner(event->owner) +{ + setTimestamp(event->timestamp); +} + +void X11Source::getTargets() +{ + auto *xcbConn = kwinApp()->x11Connection(); + /* will lead to a selection request event for the new owner */ + xcb_convert_selection(xcbConn, + selection()->window(), + selection()->atom(), + atoms->targets, + atoms->wl_selection, + timestamp()); + xcb_flush(xcbConn); +} + +using Mime = QPair; + +void X11Source::handleTargets() +{ + // receive targets + auto *xcbConn = kwinApp()->x11Connection(); + xcb_get_property_cookie_t cookie = xcb_get_property(xcbConn, + 1, + selection()->window(), + atoms->wl_selection, + XCB_GET_PROPERTY_TYPE_ANY, + 0, + 4096 + ); + auto *reply = xcb_get_property_reply(xcbConn, cookie, NULL); + if (reply == NULL) { + return; + } + if (reply->type != XCB_ATOM_ATOM) { + free(reply); + return; + } + + Mimes all; + QVector add, rm; + xcb_atom_t *value = static_cast(xcb_get_property_value(reply)); + for (uint32_t i = 0; i < reply->value_len; i++) { + if (value[i] == XCB_ATOM_NONE) { + continue; + } + + const auto mimeStrings = Selection::atomToMimeTypes(value[i]); + if (mimeStrings.isEmpty()) { + // TODO: this should never happen? assert? + continue; + } + + + const auto mimeIt = std::find_if(m_offers.begin(), m_offers.end(), + [value, i](const Mime &m) + { return m.second == value[i]; }); + + auto mimePair = Mime(mimeStrings[0], value[i]); + if (mimeIt == m_offers.end()) { + add << mimePair.first; + } else { + m_offers.removeAll(mimePair); + } + all << mimePair; + } + // all left in m_offers are not in the updated targets + for (const auto mimePair : m_offers) { + rm << mimePair.first; + } + m_offers = all; + + if (!add.isEmpty() || !rm.isEmpty()) { + Q_EMIT offersChanged(add, rm); + } + + free(reply); +} + +void X11Source::setDataSource(KWayland::Client::DataSource *ds) +{ + Q_ASSERT(ds); + if (m_ds) { + delete m_ds; + } + m_ds = ds; + + std::for_each(m_offers.begin(), m_offers.end(), + [ds](const Mime &offer){ + ds->offer(offer.first); + }); + connect(ds, &KWayland::Client::DataSource::sendDataRequested, + this, &X11Source::startTransfer); +} + +void X11Source::setOffers(const Mimes &offers) +{ + // TODO: share code with handleTargets and emit signals accordingly? + m_offers = offers; +} + +bool X11Source::handleSelNotify(xcb_selection_notify_event_t *event) +{ + if (event->requestor != selection()->window()) { + return false; + } + if (event->selection != selection()->atom()) { + return false; + } + if (event->property == XCB_ATOM_NONE) { + qCWarning(KWIN_XWL) << "Incoming X selection conversion failed"; + return true; + } + if (event->target == atoms->targets) { + handleTargets(); + return true; + } + return false; +} + +void X11Source::startTransfer(const QString &mimeName, qint32 fd) +{ + const auto mimeIt = std::find_if(m_offers.begin(), m_offers.end(), + [mimeName](const Mime &m) + { return m.first == mimeName; }); + if (mimeIt == m_offers.end()) { + qCDebug(KWIN_XWL) << "Sending X11 clipboard to Wayland failed: unsupported MIME."; + close(fd); + return; + } + + Q_EMIT transferReady((*mimeIt).second, fd); +} + + +} +} diff --git a/xwl/selection_source.h b/xwl/selection_source.h new file mode 100644 index 000000000..b4ebb14cb --- /dev/null +++ b/xwl/selection_source.h @@ -0,0 +1,155 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright 2019 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef KWIN_XWL_SELECTION_SOURCE +#define KWIN_XWL_SELECTION_SOURCE + +#include +#include + +#include + +class QSocketNotifier; + +struct xcb_selection_request_event_t; +struct xcb_xfixes_selection_notify_event_t; + +namespace KWayland +{ +namespace Client +{ +class DataSource; +} +namespace Server +{ +class DataDeviceInterface; +class DataSourceInterface; +} +} + +namespace KWin +{ +namespace Xwl +{ +class Selection; + +/** + * Base class representing a data source. + */ +class SelectionSource : public QObject +{ + Q_OBJECT +public: + SelectionSource(Selection *sel); + + xcb_timestamp_t timestamp() const { + return m_timestamp; + } + void setTimestamp(xcb_timestamp_t time) { + m_timestamp = time; + } + +protected: + Selection *selection() const { + return m_sel; + } + +private: + xcb_timestamp_t m_timestamp = XCB_CURRENT_TIME; + Selection *m_sel; +}; + +/** + * Representing a Wayland native data source. + */ +class WlSource : public SelectionSource +{ + Q_OBJECT +public: + WlSource(Selection *sel, KWayland::Server::DataDeviceInterface *ddi); + void setDataSourceIface(KWayland::Server::DataSourceInterface *dsi); + + bool handleSelRequest(xcb_selection_request_event_t *event); + void sendTargets(xcb_selection_request_event_t *event); + void sendTimestamp(xcb_selection_request_event_t *event); + + void receiveOffer(const QString &mime); + void sendSelNotify(xcb_selection_request_event_t *event, bool success); + +Q_SIGNALS: + void transferReady(xcb_selection_request_event_t *event, qint32 fd); + +private: + bool checkStartTransfer(xcb_selection_request_event_t *event); + + KWayland::Server::DataDeviceInterface *m_ddi = nullptr; + KWayland::Server::DataSourceInterface *m_dsi = nullptr; + + QVector m_offers; + QMetaObject::Connection m_offerCon; +}; + +using Mimes = QVector >; + +/** + * Representing an X data source. + */ +class X11Source : public SelectionSource +{ + Q_OBJECT +public: + X11Source(Selection *sel, xcb_xfixes_selection_notify_event_t *event); + + /** + * @param ds must exist. + * + * X11Source does not take ownership of it in general, but if the function + * is called again, it will delete the previous data source. + */ + void setDataSource(KWayland::Client::DataSource *ds); + KWayland::Client::DataSource* dataSource() const { + return m_ds; + } + void getTargets(); + + Mimes offers() const { + return m_offers; + } + void setOffers(const Mimes &offers); + + bool handleSelNotify(xcb_selection_notify_event_t *event); + +Q_SIGNALS: + void offersChanged(QVector added, QVector removed); + void transferReady(xcb_atom_t target, qint32 fd); + +private: + void handleTargets(); + void startTransfer(const QString &mimeName, qint32 fd); + + xcb_window_t m_owner; + KWayland::Client::DataSource *m_ds = nullptr; + + Mimes m_offers; +}; + +} +} + +#endif diff --git a/xwl/transfer.cpp b/xwl/transfer.cpp new file mode 100644 index 000000000..65198aa8e --- /dev/null +++ b/xwl/transfer.cpp @@ -0,0 +1,506 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright 2018 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "transfer.h" + +#include "xwayland.h" +#include "databridge.h" + +#include "atoms.h" +#include "wayland_server.h" +#include "workspace.h" +#include "abstract_client.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include + +namespace KWin { +namespace Xwl { + +// in Bytes: equals 64KB +static const uint32_t s_incrChunkSize = 63 * 1024; + +Transfer::Transfer(xcb_atom_t selection, qint32 fd, xcb_timestamp_t timestamp, QObject *parent) + : QObject(parent), + m_atom(selection), + m_fd(fd), + m_timestamp(timestamp) +{ +} + + +void Transfer::createSocketNotifier(QSocketNotifier::Type type) +{ + delete m_sn; + m_sn = new QSocketNotifier(m_fd, type, this); +} + +void Transfer::clearSocketNotifier() +{ + delete m_sn; + m_sn = nullptr; +} + +void Transfer::timeout() +{ + if (m_timeout) { + endTransfer(); + } + m_timeout = true; +} + +void Transfer::endTransfer() +{ + clearSocketNotifier(); + closeFd(); + Q_EMIT finished(); +} + +void Transfer::closeFd() +{ + if (m_fd < 0) { + return; + } + close(m_fd); + m_fd = -1; +} + +TransferWltoX::TransferWltoX(xcb_atom_t selection, xcb_selection_request_event_t *request, + qint32 fd, QObject *parent) + : Transfer(selection, fd, 0, parent), + m_request(request) +{ +} + +TransferWltoX::~TransferWltoX() +{ + delete m_request; + m_request = nullptr; +} + +void TransferWltoX::startTransferFromSource() +{ + createSocketNotifier(QSocketNotifier::Read); + connect(socketNotifier(), &QSocketNotifier::activated, this, + [this](int socket) { + Q_UNUSED(socket); + readWlSource(); + } + ); +} + +int TransferWltoX::flushSourceData() +{ + auto *xcbConn = kwinApp()->x11Connection(); + + xcb_change_property(xcbConn, + XCB_PROP_MODE_REPLACE, + m_request->requestor, + m_request->property, + m_request->target, + 8, + chunks.first().first.size(), + chunks.first().first.data()); + xcb_flush(xcbConn); + + propertyIsSet = true; + resetTimeout(); + + const auto rm = chunks.takeFirst(); + return rm.first.size(); +} + +void TransferWltoX::startIncr() +{ + Q_ASSERT(chunks.size() == 1); + + auto *xcbConn = kwinApp()->x11Connection(); + + uint32_t mask[] = { XCB_EVENT_MASK_PROPERTY_CHANGE }; + xcb_change_window_attributes (xcbConn, + m_request->requestor, + XCB_CW_EVENT_MASK, mask); + + // spec says to make the available space larger + const uint32_t chunkSpace = 1024 + s_incrChunkSize; + xcb_change_property(xcbConn, + XCB_PROP_MODE_REPLACE, + m_request->requestor, + m_request->property, + atoms->incr, + 32, 1, &chunkSpace); + xcb_flush(xcbConn); + + setIncr(true); + // first data will be flushed after the property has been deleted + // again by the requestor + flushPropOnDelete = true; + propertyIsSet = true; + Q_EMIT selNotify(m_request, true); +} + +void TransferWltoX::readWlSource() +{ + if (chunks.size() == 0 || + chunks.last().second == s_incrChunkSize) { + // append new chunk + auto next = QPair(); + next.first.resize(s_incrChunkSize); + next.second = 0; + chunks.append(next); + } + + const auto oldLen = chunks.last().second; + const auto avail = s_incrChunkSize - chunks.last().second; + Q_ASSERT(avail > 0); + + ssize_t readLen = read(fd(), chunks.last().first.data() + oldLen, avail); + if (readLen == -1) { + qCWarning(KWIN_XWL) << "Error reading in Wl data."; + + // TODO: cleanup X side? + endTransfer(); + return; + } + chunks.last().second = oldLen + readLen; + + if (readLen == 0) { + // at the fd end - complete transfer now + chunks.last().first.resize(chunks.last().second); + + if (incr()) { + // incremental transfer is to be completed now + flushPropOnDelete = true; + if (!propertyIsSet) { + // flush if target's property is not set at the moment + flushSourceData(); + } + clearSocketNotifier(); + } else { + // non incremental transfer is to be completed now, + // data can be transferred to X client via a single property set + flushSourceData(); + Q_EMIT selNotify(m_request, true); + endTransfer(); + } + } else if (chunks.last().second == s_incrChunkSize) { + // first chunk full, but not yet at fd end -> go incremental + if (incr()) { + flushPropOnDelete = true; + if (!propertyIsSet) { + // flush if target's property is not set at the moment + flushSourceData(); + } + } else { + // starting incremental transfer + startIncr(); + } + } + resetTimeout(); +} + +bool TransferWltoX::handlePropNotify(xcb_property_notify_event_t *event) +{ + if (event->window == m_request->requestor) { + if (event->state == XCB_PROPERTY_DELETE && + event->atom == m_request->property) { + handlePropDelete(); + } + return true; + } + return false; +} + +void TransferWltoX::handlePropDelete() +{ + if (!incr()) { + // non-incremental transfer: nothing to do + return; + } + propertyIsSet = false; + + if (flushPropOnDelete) { + if (!socketNotifier() && chunks.isEmpty()) { + // transfer complete + auto *xcbConn = kwinApp()->x11Connection(); + + uint32_t mask[] = {0}; + xcb_change_window_attributes (xcbConn, + m_request->requestor, + XCB_CW_EVENT_MASK, mask); + + xcb_change_property(xcbConn, + XCB_PROP_MODE_REPLACE, + m_request->requestor, + m_request->property, + m_request->target, + 8, 0, NULL); + xcb_flush(xcbConn); + flushPropOnDelete = false; + endTransfer(); + } else { + flushSourceData(); + } + } +} + +TransferXtoWl::TransferXtoWl(xcb_atom_t selection, xcb_atom_t target, qint32 fd, + xcb_timestamp_t timestamp, xcb_window_t parentWindow, + QObject *parent) + : Transfer(selection, fd, timestamp, parent) +{ + // create transfer window + auto *xcbConn = kwinApp()->x11Connection(); + m_window = xcb_generate_id(xcbConn); + const uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | + XCB_EVENT_MASK_PROPERTY_CHANGE }; + xcb_create_window(xcbConn, + XCB_COPY_FROM_PARENT, + m_window, + parentWindow, + 0, 0, + 10, 10, + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + Xwayland::self()->xcbScreen()->root_visual, + XCB_CW_EVENT_MASK, + values); + // convert selection + xcb_convert_selection(xcbConn, + m_window, + selection, + target, + atoms->wl_selection, + timestamp); + xcb_flush(xcbConn); +} + +TransferXtoWl::~TransferXtoWl() +{ + auto *xcbConn = kwinApp()->x11Connection(); + xcb_destroy_window(xcbConn, m_window); + xcb_flush(xcbConn); + + delete m_receiver; + m_receiver = nullptr; +} + +bool TransferXtoWl::handlePropNotify(xcb_property_notify_event_t *event) +{ + if (event->window == m_window) { + if (event->state == XCB_PROPERTY_NEW_VALUE && + event->atom == atoms->wl_selection) { + getIncrChunk(); + } + return true; + } + return false; +} + +bool TransferXtoWl::handleSelNotify(xcb_selection_notify_event_t *event) +{ + if (event->requestor != m_window) { + return false; + } + if (event->selection != atom()) { + return false; + } + if (event->property == XCB_ATOM_NONE) { + qCWarning(KWIN_XWL) << "Incoming X selection conversion failed"; + return true; + } + if (event->target == atoms->targets) { + qCWarning(KWIN_XWL) << "Received targets too late"; + // TODO: or allow it? + return true; + } + if (m_receiver) { + // second selection notify element - misbehaving source + + // TODO: cancel this transfer? + return True; + } + + m_receiver = new DataReceiver; + startTransfer(); + return true; +} + +void TransferXtoWl::startTransfer() +{ + auto *xcbConn = kwinApp()->x11Connection(); + auto cookie = xcb_get_property(xcbConn, + 1, + m_window, + atoms->wl_selection, + XCB_GET_PROPERTY_TYPE_ANY, + 0, + 0x1fffffff + ); + + auto *reply = xcb_get_property_reply(xcbConn, cookie, NULL); + if (reply == NULL) { + qCWarning(KWIN_XWL) << "Can't get selection property."; + endTransfer(); + return; + } + + if (reply->type == atoms->incr) { + setIncr(true); + free(reply); + } else { + setIncr(false); + // reply's ownership is transferred + m_receiver->transferFromProperty(reply); + dataSourceWrite(); + } +} + +void TransferXtoWl::getIncrChunk() +{ + if (!incr()) { + // source tries to sent incrementally, but did not announce it before + return; + } + if (!m_receiver) { + // receive mechanism has not yet been setup + return; + } + auto *xcbConn = kwinApp()->x11Connection(); + + auto cookie = xcb_get_property(xcbConn, + 0, + m_window, + atoms->wl_selection, + XCB_GET_PROPERTY_TYPE_ANY, + 0, + 0x1fffffff); + + auto *reply = xcb_get_property_reply(xcbConn, cookie, NULL); + if (reply == NULL) { + qCWarning(KWIN_XWL) << "Can't get selection property."; + endTransfer(); + return; + } + + if (xcb_get_property_value_length(reply) > 0) { + // reply's ownership is transferred + m_receiver->transferFromProperty(reply); + dataSourceWrite(); + } else { + // Transfer complete + free(reply); + endTransfer(); + } +} + +DataReceiver::~DataReceiver() +{ + if (m_propertyReply) { + free(m_propertyReply); + m_propertyReply = nullptr; + } +} + +void DataReceiver::transferFromProperty(xcb_get_property_reply_t *reply) +{ + m_propertyStart = 0; + m_propertyReply = reply; + + setData(static_cast(xcb_get_property_value(reply)), + xcb_get_property_value_length(reply)); +} + +void DataReceiver::setData(char *value, int length) +{ + // simply set data without copy + m_data = QByteArray::fromRawData(value, length); +} + +QByteArray DataReceiver::data() const +{ + return QByteArray::fromRawData(m_data.data() + m_propertyStart, + m_data.size() - m_propertyStart); +} + +void DataReceiver::partRead(int length) +{ + m_propertyStart += length; + if (m_propertyStart == m_data.size()) { + Q_ASSERT(m_propertyReply); + free(m_propertyReply); + m_propertyReply = nullptr; + } +} + +void TransferXtoWl::dataSourceWrite() +{ + QByteArray property = m_receiver->data(); + + ssize_t len = write(fd(), property.constData(), property.size()); + if (len == -1) { + qCWarning(KWIN_XWL) << "X11 to Wayland write error on fd:" << fd(); + endTransfer(); + return; + } + + m_receiver->partRead(len); + if (len == property.size()) { + // property completely transferred + if (incr()) { + clearSocketNotifier(); + auto *xcbConn = kwinApp()->x11Connection(); + xcb_delete_property(xcbConn, + m_window, + atoms->wl_selection); + xcb_flush(xcbConn); + } else { + // transfer complete + endTransfer(); + } + } else { + if (!socketNotifier()) { + createSocketNotifier(QSocketNotifier::Write); + connect(socketNotifier(), &QSocketNotifier::activated, this, + [this](int socket) { + Q_UNUSED(socket); + dataSourceWrite(); + } + ); + } + } + resetTimeout(); +} + +} +} diff --git a/xwl/transfer.h b/xwl/transfer.h new file mode 100644 index 000000000..02cc2c020 --- /dev/null +++ b/xwl/transfer.h @@ -0,0 +1,198 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright 2018 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef KWIN_XWL_TRANSFER +#define KWIN_XWL_TRANSFER + +#include +#include +#include + +#include + +namespace KWayland { +namespace Client { +class DataDevice; +class DataSource; +} +namespace Server { +class DataDeviceInterface; +} +} + +namespace KWin +{ +namespace Xwl +{ + +/* + * Represents for an arbitrary selection a data transfer between + * sender and receiver. + * + * Lives for the duration of the transfer and must be cleaned up + * externally afterwards. For that the owner should connect to the + * @c finished() signal. + */ +class Transfer : public QObject +{ + Q_OBJECT +public: + Transfer(xcb_atom_t selection, + qint32 fd, + xcb_timestamp_t timestamp, + QObject *parent = nullptr); + + virtual bool handlePropNotify(xcb_property_notify_event_t *event) = 0; + void timeout(); + xcb_timestamp_t timestamp() const { + return m_timestamp; + } + +Q_SIGNALS: + void finished(); + +protected: + void endTransfer(); + + xcb_atom_t atom() const { + return m_atom; + } + qint32 fd() const { + return m_fd; + } + + void setIncr(bool set) { + m_incr = set; + } + bool incr() const { + return m_incr; + } + void resetTimeout() { + m_timeout = false; + } + void createSocketNotifier(QSocketNotifier::Type type); + void clearSocketNotifier(); + QSocketNotifier* socketNotifier() const { + return m_sn; + } +private: + void closeFd(); + + xcb_atom_t m_atom; + qint32 m_fd; + xcb_timestamp_t m_timestamp = XCB_CURRENT_TIME; + + QSocketNotifier *m_sn = nullptr; + bool m_incr = false; + bool m_timeout = false; +}; + +/* + * Represents a transfer from a Wayland native source to an X window. + */ +class TransferWltoX : public Transfer +{ + Q_OBJECT +public: + TransferWltoX(xcb_atom_t selection, + xcb_selection_request_event_t *request, + qint32 fd, + QObject *parent = nullptr); + ~TransferWltoX(); + + void startTransferFromSource(); + bool handlePropNotify(xcb_property_notify_event_t *event) override; + +Q_SIGNALS: + void selNotify(xcb_selection_request_event_t *event, bool success); + +private: + void startIncr(); + void readWlSource(); + int flushSourceData(); + void handlePropDelete(); + + xcb_selection_request_event_t *m_request = nullptr; + + /* contains all received data portioned in chunks + * TODO: explain second QPair component + */ + QVector > chunks; + + bool propertyIsSet = false; + bool flushPropOnDelete = false; +}; + +/* + * Helper class for X to Wl transfers + */ +class DataReceiver +{ +public: + virtual ~DataReceiver(); + + void transferFromProperty(xcb_get_property_reply_t *reply); + + + virtual void setData(char *value, int length); + QByteArray data() const; + + void partRead(int length); + +protected: + void setDataInternal(QByteArray data) { + m_data = data; + } + +private: + xcb_get_property_reply_t *m_propertyReply = nullptr; + int m_propertyStart = 0; + QByteArray m_data; +}; + +/* + * Represents a transfer from an X window to a Wayland native client. + */ +class TransferXtoWl : public Transfer +{ + Q_OBJECT +public: + TransferXtoWl(xcb_atom_t selection, + xcb_atom_t target, + qint32 fd, + xcb_timestamp_t timestamp, xcb_window_t parentWindow, + QObject *parent = nullptr); + ~TransferXtoWl(); + + bool handleSelNotify(xcb_selection_notify_event_t *event); + bool handlePropNotify(xcb_property_notify_event_t *event) override; + +private: + void dataSourceWrite(); + void startTransfer(); + void getIncrChunk(); + + xcb_window_t m_window; + DataReceiver *m_receiver = nullptr; +}; + +} +} + +#endif diff --git a/xwl/xwayland.cpp b/xwl/xwayland.cpp index 96f738738..392c15048 100644 --- a/xwl/xwayland.cpp +++ b/xwl/xwayland.cpp @@ -1,245 +1,274 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2014 Martin Gräßlin Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "xwayland.h" +#include "databridge.h" + #include "wayland_server.h" #include "main_wayland.h" #include "utils.h" #include #include "xcbutils.h" #include #include #include #include #include #include #include // system #ifdef HAVE_UNISTD_H #include #endif #if HAVE_SYS_PROCCTL_H #include #endif #include #include static void readDisplay(int pipe) { QFile readPipe; if (!readPipe.open(pipe, QIODevice::ReadOnly)) { std::cerr << "FATAL ERROR failed to open pipe to start X Server" << std::endl; exit(1); } QByteArray displayNumber = readPipe.readLine(); displayNumber.prepend(QByteArray(":")); displayNumber.remove(displayNumber.size() -1, 1); std::cout << "X-Server started on display " << displayNumber.constData() << std::endl; setenv("DISPLAY", displayNumber.constData(), true); // close our pipe close(pipe); } namespace KWin { namespace Xwl { +Xwayland *s_self = nullptr; +Xwayland* Xwayland::self() +{ + return s_self; +} + Xwayland::Xwayland(ApplicationWaylandAbstract *app, QObject *parent) : QObject(parent), m_app(app) { + s_self = this; } Xwayland::~Xwayland() { disconnect(m_xwaylandFailConnection); if (m_app->x11Connection()) { Xcb::setInputFocus(XCB_INPUT_FOCUS_POINTER_ROOT); m_app->destroyAtoms(); Q_EMIT m_app->x11ConnectionAboutToBeDestroyed(); xcb_disconnect(m_app->x11Connection()); m_app->setX11Connection(nullptr); } if (m_xwaylandProcess) { m_xwaylandProcess->terminate(); while (m_xwaylandProcess->state() != QProcess::NotRunning) { m_app->processEvents(QEventLoop::WaitForMoreEvents); } waylandServer()->destroyXWaylandConnection(); } + s_self = nullptr; } void Xwayland::init() { int pipeFds[2]; if (pipe(pipeFds) != 0) { std::cerr << "FATAL ERROR failed to create pipe to start Xwayland " << std::endl; Q_EMIT criticalError(1); return; } int sx[2]; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { std::cerr << "FATAL ERROR: failed to open socket to open XCB connection" << std::endl; Q_EMIT criticalError(1); return; } int fd = dup(sx[1]); if (fd < 0) { std::cerr << "FATAL ERROR: failed to open socket to open XCB connection" << std::endl; Q_EMIT criticalError(20); return; } const int waylandSocket = waylandServer()->createXWaylandConnection(); if (waylandSocket == -1) { std::cerr << "FATAL ERROR: failed to open socket for Xwayland" << std::endl; Q_EMIT criticalError(1); return; } const int wlfd = dup(waylandSocket); if (wlfd < 0) { std::cerr << "FATAL ERROR: failed to open socket for Xwayland" << std::endl; Q_EMIT criticalError(20); return; } m_xcbConnectionFd = sx[0]; m_xwaylandProcess = new Process(this); m_xwaylandProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel); m_xwaylandProcess->setProgram(QStringLiteral("Xwayland")); QProcessEnvironment env = m_app->processStartupEnvironment(); env.insert("WAYLAND_SOCKET", QByteArray::number(wlfd)); env.insert("EGL_PLATFORM", QByteArrayLiteral("DRM")); m_xwaylandProcess->setProcessEnvironment(env); m_xwaylandProcess->setArguments({QStringLiteral("-displayfd"), QString::number(pipeFds[1]), QStringLiteral("-rootless"), QStringLiteral("-wm"), QString::number(fd)}); m_xwaylandFailConnection = connect(m_xwaylandProcess, static_cast(&QProcess::error), this, [this] (QProcess::ProcessError error) { if (error == QProcess::FailedToStart) { std::cerr << "FATAL ERROR: failed to start Xwayland" << std::endl; } else { std::cerr << "FATAL ERROR: Xwayland failed, going to exit now" << std::endl; } Q_EMIT criticalError(1); } ); const int xDisplayPipe = pipeFds[0]; connect(m_xwaylandProcess, &QProcess::started, this, [this, xDisplayPipe] { QFutureWatcher *watcher = new QFutureWatcher(this); QObject::connect(watcher, &QFutureWatcher::finished, this, &Xwayland::continueStartupWithX, Qt::QueuedConnection); QObject::connect(watcher, &QFutureWatcher::finished, watcher, &QFutureWatcher::deleteLater, Qt::QueuedConnection); watcher->setFuture(QtConcurrent::run(readDisplay, xDisplayPipe)); } ); m_xwaylandProcess->start(); close(pipeFds[1]); } +void Xwayland::prepareDestroy() +{ + delete m_dataBridge; + m_dataBridge = nullptr; +} + void Xwayland::createX11Connection() { int screenNumber = 0; xcb_connection_t *c = nullptr; if (m_xcbConnectionFd == -1) { c = xcb_connect(nullptr, &screenNumber); } else { c = xcb_connect_to_fd(m_xcbConnectionFd, nullptr); } if (int error = xcb_connection_has_error(c)) { std::cerr << "FATAL ERROR: Creating connection to XServer failed: " << error << std::endl; Q_EMIT criticalError(1); return; } + xcb_screen_iterator_t iter = xcb_setup_roots_iterator(xcb_get_setup(c)); + m_xcbScreen = iter.data; + Q_ASSERT(m_xcbScreen); + m_app->setX11Connection(c); // we don't support X11 multi-head in Wayland m_app->setX11ScreenNumber(screenNumber); m_app->setX11RootWindow(defaultScreen()->root); } void Xwayland::continueStartupWithX() { createX11Connection(); auto *xcbConn = m_app->x11Connection(); if (!xcbConn) { // about to quit Q_EMIT criticalError(1); return; } QSocketNotifier *notifier = new QSocketNotifier(xcb_get_file_descriptor(xcbConn), QSocketNotifier::Read, this); auto processXcbEvents = [this, xcbConn] { while (auto event = xcb_poll_for_event(xcbConn)) { + if (m_dataBridge->filterEvent(event)) { + free(event); + continue; + } long result = 0; QThread::currentThread()->eventDispatcher()->filterNativeEvent(QByteArrayLiteral("xcb_generic_event_t"), event, &result); free(event); } xcb_flush(xcbConn); }; connect(notifier, &QSocketNotifier::activated, this, processXcbEvents); connect(QThread::currentThread()->eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, processXcbEvents); connect(QThread::currentThread()->eventDispatcher(), &QAbstractEventDispatcher::awake, this, processXcbEvents); + xcb_prefetch_extension_data(xcbConn, &xcb_xfixes_id); + m_xfixes = xcb_get_extension_data(xcbConn, &xcb_xfixes_id); + // create selection owner for WM_S0 - magic X display number expected by XWayland KSelectionOwner owner("WM_S0", xcbConn, m_app->x11RootWindow()); owner.claim(true); m_app->createAtoms(); m_app->setupEventFilters(); + m_dataBridge = new DataBridge; + // Check whether another windowmanager is running const uint32_t maskValues[] = {XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT}; ScopedCPointer redirectCheck(xcb_request_check(connection(), xcb_change_window_attributes_checked(connection(), rootWindow(), XCB_CW_EVENT_MASK, maskValues))); if (!redirectCheck.isNull()) { fputs(i18n("kwin_wayland: an X11 window manager is running on the X11 Display.\n").toLocal8Bit().constData(), stderr); Q_EMIT criticalError(1); return; } auto env = m_app->processStartupEnvironment(); env.insert(QStringLiteral("DISPLAY"), QString::fromUtf8(qgetenv("DISPLAY"))); m_app->setProcessStartupEnvironment(env); m_app->startSession(); m_app->createWorkspace(); Xcb::sync(); // Trigger possible errors, there's still a chance to abort m_app->notifyKSplash(); } } } diff --git a/xwl/xwayland.h b/xwl/xwayland.h index c07c6a1b5..57d6c54c0 100644 --- a/xwl/xwayland.h +++ b/xwl/xwayland.h @@ -1,62 +1,79 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_XWL_XWAYLAND #define KWIN_XWL_XWAYLAND #include +#include + class QProcess; class xcb_screen_t; namespace KWin { class ApplicationWaylandAbstract; namespace Xwl { +class DataBridge; class Xwayland : public QObject { Q_OBJECT public: + static Xwayland* self(); + Xwayland(ApplicationWaylandAbstract *app, QObject *parent = nullptr); virtual ~Xwayland(); void init(); + void prepareDestroy(); + + xcb_screen_t *xcbScreen() const { + return m_xcbScreen; + } + const xcb_query_extension_reply_t *xfixes() const { + return m_xfixes; + } Q_SIGNALS: void criticalError(int code); private: void createX11Connection(); void continueStartupWithX(); int m_xcbConnectionFd = -1; QProcess *m_xwaylandProcess = nullptr; QMetaObject::Connection m_xwaylandFailConnection; + xcb_screen_t *m_xcbScreen = nullptr; + const xcb_query_extension_reply_t *m_xfixes = nullptr; + DataBridge *m_dataBridge = nullptr; + ApplicationWaylandAbstract *m_app; }; } } #endif