diff --git a/CMakeLists.txt b/CMakeLists.txt index 241981909..56f8df206 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,638 +1,639 @@ project(KWIN) set(PROJECT_VERSION "5.6.90") set(PROJECT_VERSION_MAJOR 5) cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) set(QT_MIN_VERSION "5.5.0") set(KF5_MIN_VERSION "5.12.0") set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH} ) find_package(ECM 0.0.11 REQUIRED NO_MODULE) include(FeatureSummary) include(WriteBasicConfigVersionFile) include(GenerateExportHeader) # where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is checked set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Concurrent Core DBus Quick QuickWidgets Script UiTools Widgets X11Extras ) find_package(Qt5Test ${QT_MIN_VERSION} CONFIG QUIET) set_package_properties(Qt5Test PROPERTIES PURPOSE "Required for tests" TYPE OPTIONAL ) add_feature_info("Qt5Test" Qt5Test_FOUND "Required for building tests") if (NOT Qt5Test_FOUND) set(BUILD_TESTING OFF CACHE BOOL "Build the testing tree.") endif() include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMInstallIcons) include(ECMOptionalAddSubdirectory) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0 -DQT_USE_QSTRINGBUILDER) # require at least gcc 4.8 if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") if ("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS "4.8") message(SEND_ERROR "Version ${CMAKE_CXX_COMPILER_VERSION} of the ${CMAKE_CXX_COMPILER_ID} C++ compiler is not supported. Please use version 4.8 or later.") endif() endif() find_package(Qt5Multimedia QUIET) set_package_properties(Qt5Multimedia PROPERTIES PURPOSE "Runtime-only dependency for effect video playback" TYPE RUNTIME ) # required frameworks by Core find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Config ConfigWidgets CoreAddons Crash GlobalAccel I18n Init Notifications Package Plasma WidgetsAddons WindowSystem IconThemes IdleTime ) # required frameworks by config modules find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Completion Declarative KCMUtils KIO NewStuff Service XmlGui ) find_package(Threads) set_package_properties(Threads PROPERTIES PURPOSE "Needed for VirtualTerminal support in KWin Wayland" TYPE REQUIRED ) # optional frameworks find_package(KF5Activities ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5Activities PROPERTIES PURPOSE "Enable building of KWin with kactivities support" TYPE OPTIONAL ) add_feature_info("KF5Activities" KF5Activities_FOUND "Enable building of KWin with kactivities support") find_package(KF5DocTools ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5DocTools PROPERTIES PURPOSE "Enable building documentation" TYPE OPTIONAL ) add_feature_info("KF5DocTools" KF5DocTools_FOUND "Enable building documentation") find_package(KDecoration2 CONFIG REQUIRED) find_package(KF5Wayland CONFIG REQUIRED) set_package_properties(KF5Wayland PROPERTIES TYPE REQUIRED ) find_package(KScreenLocker CONFIG REQUIRED) set_package_properties(KScreenLocker PROPERTIES TYPE REQUIRED PURPOSE "For screenlocker integration in kwin_wayland") find_package(Breeze ${PROJECT_VERSION} CONFIG) set_package_properties(Breeze PROPERTIES TYPE OPTIONAL PURPOSE "For setting the default window decoration plugin") if(${Breeze_FOUND}) if(${BREEZE_WITH_KDECORATION}) set(HAVE_BREEZE_DECO true) else() set(HAVE_BREEZE_DECO FALSE) endif() else() set(HAVE_BREEZE_DECO FALSE) endif() add_feature_info("Breeze-Decoration" HAVE_BREEZE_DECO "Default decoration plugin Breeze") find_package(EGL) set_package_properties(EGL PROPERTIES TYPE RUNTIME PURPOSE "Required to build KWin with EGL support" ) find_package(epoxy) set_package_properties(epoxy PROPERTIES DESCRIPTION "libepoxy" URL "http://github.com/anholt/libepoxy" TYPE REQUIRED PURPOSE "OpenGL dispatch library" ) find_package(Wayland 1.2 REQUIRED COMPONENTS Cursor OPTIONAL_COMPONENTS Egl) set_package_properties(Wayland PROPERTIES TYPE REQUIRED PURPOSE "Required for building KWin with Wayland support" ) add_feature_info("Wayland::EGL" Wayland_Egl_FOUND "Enable building of Wayland backend and QPA with EGL support.") set(HAVE_WAYLAND_EGL FALSE) if(Wayland_Egl_FOUND) set(HAVE_WAYLAND_EGL TRUE) endif() find_package(XKB 0.4.1) set_package_properties(XKB PROPERTIES TYPE REQUIRED PURPOSE "Required for building KWin with Wayland support" ) find_package(Libinput 0.10) set_package_properties(Libinput PROPERTIES TYPE OPTIONAL PURPOSE "Required for input handling on Wayland.") find_package(UDev) set_package_properties(UDev PROPERTIES URL "http://www.freedesktop.org/software/systemd/libudev/" DESCRIPTION "Linux device library." TYPE OPTIONAL PURPOSE "Required for input handling on Wayland." ) set(HAVE_INPUT FALSE) if (Libinput_FOUND AND UDEV_FOUND) set(HAVE_INPUT TRUE) endif() find_package(Libdrm) set_package_properties(Libdrm PROPERTIES TYPE OPTIONAL PURPOSE "Required for drm output on Wayland.") set(HAVE_DRM FALSE) if(Libdrm_FOUND AND UDEV_FOUND) set(HAVE_DRM TRUE) endif() find_package(gbm) set_package_properties(gbm PROPERTIES TYPE OPTIONAL PURPOSE "Required for egl ouput of drm backend.") set(HAVE_GBM FALSE) if(HAVE_DRM AND gbm_FOUND) set(HAVE_GBM TRUE) endif() find_package(libhybris) set_package_properties(libhybris PROPERTIES TYPE OPTIONAL PURPOSE "Required for libhybris backend") set(HAVE_LIBHYBRIS ${libhybris_FOUND}) find_package(X11) set_package_properties(X11 PROPERTIES DESCRIPTION "X11 libraries" URL "http://www.x.org" TYPE REQUIRED ) add_feature_info("XInput" X11_Xinput_FOUND "Required for poll-free mouse cursor updates") set(HAVE_X11_XINPUT ${X11_Xinput_FOUND}) # All the required XCB components find_package(XCB 1.10 REQUIRED COMPONENTS XCB XFIXES DAMAGE COMPOSITE SHAPE SYNC RENDER RANDR KEYSYMS IMAGE SHM GLX CURSOR OPTIONAL_COMPONENTS ICCCM ) set_package_properties(XCB PROPERTIES TYPE REQUIRED) # and the optional XCB dependencies if (XCB_ICCCM_VERSION VERSION_LESS "0.4") set(XCB_ICCCM_FOUND FALSE) endif() add_feature_info("XCB-ICCCM" XCB_ICCCM_FOUND "Required for building test applications for KWin") find_package(X11_XCB) set_package_properties(X11_XCB PROPERTIES PURPOSE "Required for building X11 windowed backend of kwin_wayland" TYPE OPTIONAL) # dependencies for QPA plugin find_package(Qt5PlatformSupport REQUIRED) find_package(Freetype REQUIRED) set_package_properties(Freetype PROPERTIES DESCRIPTION "A font rendering engine" URL "http://www.freetype.org" TYPE REQUIRED PURPOSE "Needed for KWin's QPA plugin." ) find_package(Fontconfig REQUIRED) set_package_properties(Fontconfig PROPERTIES DESCRIPTION "Font access configuration library" URL "http://www.freedesktop.org/wiki/Software/fontconfig" TYPE REQUIRED PURPOSE "Needed for KWin's QPA plugin." ) find_package(Xwayland) set_package_properties(Xwayland PROPERTIES URL "http://x.org" DESCRIPTION "Xwayland X server" TYPE RUNTIME PURPOSE "Needed for running kwin_wayland" ) ########### configure tests ############### include(CMakeDependentOption) option(KWIN_BUILD_DECORATIONS "Enable building of KWin decorations." ON) option(KWIN_BUILD_KCMS "Enable building of KWin configuration modules." ON) option(KWIN_BUILD_TABBOX "Enable building of KWin Tabbox functionality" ON) option(KWIN_BUILD_XRENDER_COMPOSITING "Enable building of KWin with XRender Compositing support" ON) cmake_dependent_option(KWIN_BUILD_ACTIVITIES "Enable building of KWin with kactivities support" ON "KF5Activities_FOUND" OFF) # Binary name of KWin set(KWIN_NAME "kwin") set(KWIN_INTERNAL_NAME_X11 "kwin_x11") set(KWIN_INTERNAL_NAME_WAYLAND "kwin_wayland") set(KWIN_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) # KWIN_HAVE_XRENDER_COMPOSITING - whether XRender-based compositing support is available: may be disabled if( KWIN_BUILD_XRENDER_COMPOSITING ) set( KWIN_HAVE_XRENDER_COMPOSITING 1 ) endif() include_directories(${XKB_INCLUDE_DIR}) include_directories(${epoxy_INCLUDE_DIR}) set(HAVE_EPOXY_GLX ${epoxy_HAS_GLX}) # for things that are also used by kwin libraries configure_file(libkwineffects/kwinconfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/libkwineffects/kwinconfig.h ) # for kwin internal things set(HAVE_X11_XCB ${X11_XCB_FOUND}) include(CheckIncludeFile) include(CheckIncludeFiles) include(CheckSymbolExists) check_include_files(unistd.h HAVE_UNISTD_H) check_include_files(malloc.h HAVE_MALLOC_H) check_include_file("sys/prctl.h" HAVE_SYS_PRCTL_H) check_symbol_exists(PR_SET_DUMPABLE "sys/prctl.h" HAVE_PR_SET_DUMPABLE) 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") configure_file(config-kwin.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kwin.h ) ########### global ############### set(kwin_effects_dbus_xml ${CMAKE_CURRENT_SOURCE_DIR}/org.kde.kwin.Effects.xml) include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR}/libkwineffects ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/libkwineffects ${CMAKE_CURRENT_SOURCE_DIR}/effects ${CMAKE_CURRENT_SOURCE_DIR}/tabbox ) add_subdirectory( libkwineffects ) add_subdirectory( killer ) if(KWIN_BUILD_KCMS) add_subdirectory( kcmkwin ) endif() add_subdirectory( data ) add_subdirectory( effects ) add_subdirectory( scripts ) add_subdirectory( tabbox ) add_subdirectory(scripting) add_definitions(-DKDE_DEFAULT_DEBUG_AREA=1212) ########### next target ############### set(kwin_KDEINIT_SRCS workspace.cpp dbusinterface.cpp abstract_client.cpp client.cpp client_machine.cpp cursor.cpp debug_console.cpp tabgroup.cpp focuschain.cpp globalshortcuts.cpp input.cpp input_event.cpp keyboard_input.cpp pointer_input.cpp touch_input.cpp netinfo.cpp placement.cpp atoms.cpp utils.cpp layers.cpp main.cpp options.cpp outline.cpp events.cpp killwindow.cpp geometrytip.cpp screens.cpp shadow.cpp sm.cpp group.cpp manage.cpp overlaywindow.cpp activation.cpp useractions.cpp geometry.cpp rules.cpp composite.cpp toplevel.cpp unmanaged.cpp scene.cpp scene_xrender.cpp scene_opengl.cpp scene_qpainter.cpp thumbnailitem.cpp lanczosfilter.cpp deleted.cpp effects.cpp effectloader.cpp virtualdesktops.cpp xcbutils.cpp x11eventfilter.cpp logind.cpp screenedge.cpp scripting/scripting.cpp scripting/workspace_wrapper.cpp scripting/meta.cpp scripting/scriptedeffect.cpp scripting/scriptingutils.cpp scripting/timer.cpp scripting/scripting_model.cpp scripting/dbuscall.cpp scripting/screenedgeitem.cpp scripting/scripting_logging.cpp decorations/decoratedclient.cpp decorations/decorationbridge.cpp decorations/decorationpalette.cpp decorations/settings.cpp decorations/decorationrenderer.cpp decorations/decorations_logging.cpp abstract_egl_backend.cpp platform.cpp shell_client.cpp wayland_server.cpp wayland_cursor_theme.cpp + virtualkeyboard.cpp ) if(KWIN_BUILD_TABBOX) set( kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} tabbox/tabbox.cpp tabbox/clientmodel.cpp tabbox/desktopchain.cpp tabbox/desktopmodel.cpp tabbox/switcheritem.cpp tabbox/tabboxconfig.cpp tabbox/tabboxhandler.cpp tabbox/tabbox_logging.cpp ) endif() if(KWIN_BUILD_ACTIVITIES) set( kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} activities.cpp ) endif() if(UDEV_FOUND) set(kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} udev.cpp ) endif() if(HAVE_INPUT) set(kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} libinput/context.cpp libinput/connection.cpp libinput/device.cpp libinput/events.cpp libinput/libinput_logging.cpp virtual_terminal.cpp ) endif() kconfig_add_kcfg_files(kwin_KDEINIT_SRCS settings.kcfgc) qt5_add_dbus_adaptor( kwin_KDEINIT_SRCS org.kde.KWin.xml dbusinterface.h KWin::DBusInterface ) qt5_add_dbus_adaptor( kwin_KDEINIT_SRCS org.kde.kwin.Compositing.xml dbusinterface.h KWin::CompositorDBusInterface ) qt5_add_dbus_adaptor( kwin_KDEINIT_SRCS ${kwin_effects_dbus_xml} effects.h KWin::EffectsHandlerImpl ) qt5_add_dbus_interface( kwin_KDEINIT_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/org.freedesktop.ScreenSaver.xml screenlocker_interface) qt5_add_resources( kwin_KDEINIT_SRCS resources.qrc ) ki18n_wrap_ui(kwin_KDEINIT_SRCS debug_console.ui shortcutdialog.ui ) ########### target link libraries ############### set(kwin_OWN_LIBS kwineffects kwinxrenderutils kwin4_effect_builtins ) set(kwin_QT_LIBS Qt5::Concurrent Qt5::DBus Qt5::Quick Qt5::Script Qt5::X11Extras ) set(kwin_KDE_LIBS KF5::ConfigCore KF5::CoreAddons KF5::ConfigWidgets KF5::GlobalAccel KF5::GlobalAccelPrivate KF5::I18n KF5::Notifications KF5::Package KF5::Plasma KF5::WindowSystem KDecoration2::KDecoration KDecoration2::KDecoration2Private PW::KScreenLocker ) set(kwin_XLIB_LIBS ${X11_X11_LIB} ${X11_ICE_LIB} ${X11_SM_LIB} ) if(X11_Xinput_FOUND) set(kwin_XLIB_LIBS ${kwin_XLIB_LIBS} ${X11_Xinput_LIB}) endif() set(kwin_XCB_LIBS XCB::XCB XCB::XFIXES XCB::DAMAGE XCB::COMPOSITE XCB::SHAPE XCB::SYNC XCB::RENDER XCB::RANDR XCB::KEYSYMS XCB::SHM XCB::GLX XCB::CURSOR ) set(kwin_WAYLAND_LIBS XKB::XKB KF5::WaylandClient KF5::WaylandServer Wayland::Cursor ${CMAKE_THREAD_LIBS_INIT} ) if(KWIN_BUILD_ACTIVITIES) set(kwin_KDE_LIBS ${kwin_KDE_LIBS} KF5::Activities) endif() set(kwinLibs ${kwin_OWN_LIBS} ${kwin_QT_LIBS} ${kwin_KDE_LIBS} ${kwin_XLIB_LIBS} ${kwin_XCB_LIBS} ${kwin_WAYLAND_LIBS} ) if(UDEV_FOUND) set(kwinLibs ${kwinLibs} ${UDEV_LIBS}) endif() if(HAVE_INPUT) set(kwinLibs ${kwinLibs} Libinput::Libinput) endif() add_library(kwin SHARED ${kwin_KDEINIT_SRCS}) set_target_properties(kwin PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} ) target_link_libraries(kwin ${kwinLibs}) generate_export_header(kwin EXPORT_FILE_NAME kwin_export.h) target_link_libraries(kwin kwinglutils ${epoxy_LIBRARY}) # -ldl used by OpenGL code find_library(DL_LIBRARY dl) if (DL_LIBRARY) target_link_libraries(kwin ${DL_LIBRARY}) endif() kf5_add_kdeinit_executable(kwin_x11 main_x11.cpp) target_link_libraries(kdeinit_kwin_x11 kwin KF5::Crash) install(TARGETS kwin ${INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP ) install(TARGETS kdeinit_kwin_x11 ${INSTALL_TARGETS_DEFAULT_ARGS} ) install(TARGETS kwin_x11 ${INSTALL_TARGETS_DEFAULT_ARGS} ) add_executable(kwin_wayland main_wayland.cpp) target_link_libraries(kwin_wayland kwin) install(TARGETS kwin_wayland ${INSTALL_TARGETS_DEFAULT_ARGS} ) add_subdirectory(plugins) ########### install files ############### install( FILES kwin.kcfg DESTINATION ${KCFG_INSTALL_DIR} RENAME ${KWIN_NAME}.kcfg ) install( FILES kwin.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR} RENAME ${KWIN_NAME}.notifyrc) install( FILES org.kde.KWin.xml org.kde.kwin.Compositing.xml org.kde.kwin.Effects.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/kwin_export.h DESTINATION ${INCLUDE_INSTALL_DIR} COMPONENT Devel) # Install the KWin/Script service type install( FILES scripting/kwinscript.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR} ) ecm_install_icons( ICONS 16-apps-kwin.png 32-apps-kwin.png 48-apps-kwin.png sc-apps-kwin.svgz DESTINATION ${ICON_INSTALL_DIR} THEME hicolor ) add_subdirectory(qml) add_subdirectory(autotests) add_subdirectory(tests) if (KF5DocTools_FOUND) add_subdirectory(doc) endif() feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) include(ECMPackageConfigHelpers) set(CMAKECONFIG_INSTALL_DIR "${CMAKECONFIG_INSTALL_PREFIX}/KWinDBusInterface") ecm_configure_package_config_file(KWinDBusInterfaceConfig.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/KWinDBusInterfaceConfig.cmake" PATH_VARS KDE_INSTALL_DBUSINTERFACEDIR INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/KWinDBusInterfaceConfig.cmake DESTINATION ${CMAKECONFIG_INSTALL_DIR}) diff --git a/main_wayland.cpp b/main_wayland.cpp index add8013a2..15d45129d 100644 --- a/main_wayland.cpp +++ b/main_wayland.cpp @@ -1,723 +1,725 @@ /******************************************************************** 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 "wayland_server.h" #include "xcbutils.h" // KWayland #include #include // KDE #include #include #include // Qt #include #include #include #include #include #include #include #include #include #include #include #include // system #ifdef HAVE_UNISTD_H #include #endif // HAVE_UNISTD_H #if HAVE_SYS_PRCTL_H #include #endif #if HAVE_SYS_PROCCTL_H #include #include #endif #include #include namespace KWin { static void sighandler(int) { QApplication::exit(); } static void readDisplay(int pipe); //************************************ // ApplicationWayland //************************************ ApplicationWayland::ApplicationWayland(int &argc, char **argv) : Application(OperationModeWaylandAndX11, argc, argv) { } ApplicationWayland::~ApplicationWayland() { if (!waylandServer()) { return; } 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(); } destroyWorkspace(); waylandServer()->dispatch(); disconnect(m_xwaylandFailConnection); if (x11Connection()) { Xcb::setInputFocus(XCB_INPUT_FOCUS_POINTER_ROOT); destroyAtoms(); emit x11ConnectionAboutToBeDestroyed(); xcb_disconnect(x11Connection()); setX11Connection(nullptr); } if (m_xwaylandProcess) { m_xwaylandProcess->terminate(); while (m_xwaylandProcess->state() != QProcess::NotRunning) { processEvents(QEventLoop::WaitForMoreEvents); } waylandServer()->destroyXWaylandConnection(); } waylandServer()->terminateClientConnections(); destroyCompositor(); } void ApplicationWayland::performStartup() { setOperationMode(m_startXWayland ? OperationModeXwayland : OperationModeWaylandAndX11); // first load options - done internally by a different thread createOptions(); waylandServer()->createInternalConnection(); // try creating the Wayland Backend createInput(); + VirtualKeyboard::create(this); createBackend(); } 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; ::exit(1); } ); platform()->init(); } void ApplicationWayland::continueStartupWithScreens() { disconnect(kwinApp()->platform(), &Platform::screensQueried, this, &ApplicationWayland::continueStartupWithScreens); createScreens(); if (!m_startXWayland) { continueStartupWithX(); return; } createCompositor(); connect(Compositor::self(), &Compositor::sceneCreated, this, &ApplicationWayland::startXwaylandServer); } void ApplicationWayland::continueStartupWithX() { createX11Connection(); xcb_connection_t *c = x11Connection(); if (!c) { // about to quit return; } QSocketNotifier *notifier = new QSocketNotifier(xcb_get_file_descriptor(c), QSocketNotifier::Read, this); auto processXcbEvents = [this, c] { while (auto event = xcb_poll_for_event(c)) { updateX11Time(event); long result = 0; if (QThread::currentThread()->eventDispatcher()->filterNativeEvent(QByteArrayLiteral("xcb_generic_event_t"), event, &result)) { free(event); continue; } if (Workspace::self()) { Workspace::self()->workspaceEvent(event); } free(event); } xcb_flush(c); }; connect(notifier, &QSocketNotifier::activated, this, processXcbEvents); connect(QThread::currentThread()->eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, processXcbEvents); connect(QThread::currentThread()->eventDispatcher(), &QAbstractEventDispatcher::awake, this, processXcbEvents); // create selection owner for WM_S0 - magic X display number expected by XWayland KSelectionOwner owner("WM_S0", c, x11RootWindow()); owner.claim(true); createAtoms(); setupEventFilters(); // 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); ::exit(1); } if (!m_inputMethodServerToStart.isEmpty()) { int socket = dup(waylandServer()->createInputMethodConnection()); if (socket >= 0) { QProcessEnvironment environment = m_environment; 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(); } } m_environment.insert(QStringLiteral("DISPLAY"), QString::fromUtf8(qgetenv("DISPLAY"))); // start session if (!m_sessionArgument.isEmpty()) { QProcess *p = new Process(this); p->setProcessChannelMode(QProcess::ForwardedErrorChannel); p->setProcessEnvironment(m_environment); 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(m_environment); p->start(application); } } createWorkspace(); Xcb::sync(); // Trigger possible errors, there's still a chance to abort notifyKSplash(); } void ApplicationWayland::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; exit(1); return; } setX11Connection(c); // we don't support X11 multi-head in Wayland setX11ScreenNumber(screenNumber); setX11RootWindow(defaultScreen()->root); } void ApplicationWayland::startXwaylandServer() { disconnect(Compositor::self(), &Compositor::sceneCreated, this, &ApplicationWayland::startXwaylandServer); int pipeFds[2]; if (pipe(pipeFds) != 0) { std::cerr << "FATAL ERROR failed to create pipe to start Xwayland " << std::endl; exit(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; exit(1); return; } int fd = dup(sx[1]); if (fd < 0) { std::cerr << "FATAL ERROR: failed to open socket to open XCB connection" << std::endl; exit(20); return; } const int waylandSocket = waylandServer()->createXWaylandConnection(); if (waylandSocket == -1) { std::cerr << "FATAL ERROR: failed to open socket for Xwayland" << std::endl; exit(1); return; } const int wlfd = dup(waylandSocket); if (wlfd < 0) { std::cerr << "FATAL ERROR: failed to open socket for Xwayland" << std::endl; exit(20); return; } m_xcbConnectionFd = sx[0]; m_xwaylandProcess = new Process(kwinApp()); m_xwaylandProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel); m_xwaylandProcess->setProgram(QStringLiteral("Xwayland")); QProcessEnvironment env = m_environment; env.insert("WAYLAND_SOCKET", QByteArray::number(wlfd)); 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, [] (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; } exit(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, &ApplicationWayland::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]); } 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); } 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"))) { // 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 } } // namespace int main(int argc, char * argv[]) { KWin::disablePtrace(); KWin::Application::setupMalloc(); KWin::Application::setupLocalizedString(); 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); // 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"); - qunsetenv("QT_IM_MODULE"); + qputenv("QT_IM_MODULE", "qtvirtualkeyboard"); qputenv("QSG_RENDER_LOOP", "basic"); #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling); #endif 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(); 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 hasWindowedOption = hasPlugin(KWin::s_x11Plugin) || hasPlugin(KWin::s_waylandPlugin); 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 windowedOption(QStringLiteral("windowed"), i18n("Use a nested compositor in windowed mode.")); QCommandLineOption framebufferOption(QStringLiteral("framebuffer"), i18n("Render to framebuffer.")); QCommandLineOption framebufferDeviceOption(QStringLiteral("fb-device"), i18n("The framebuffer device to render to."), QStringLiteral("fbdev")); framebufferDeviceOption.setDefaultValue(QStringLiteral("/dev/fb0")); 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 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 (hasWindowedOption) { parser.addOption(windowedOption); } 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); } if (hasOutputCountOption) { parser.addOption(outputCountOption); } #if HAVE_LIBHYBRIS QCommandLineOption hwcomposerOption(QStringLiteral("hwcomposer"), i18n("Use libhybris hwcomposer")); if (hasHwcomposerOption) { parser.addOption(hwcomposerOption); } #endif #if HAVE_INPUT QCommandLineOption libinputOption(QStringLiteral("libinput"), i18n("Enable libinput support for input events processing. Note: never use in a nested session.")); parser.addOption(libinputOption); #endif #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 exitWithSessionOption(QStringLiteral("exit-with-session"), i18n("Exit after the session application, which is started by KWin, closed."), QStringLiteral("/path/to/session")); parser.addOption(exitWithSessionOption); #ifdef KWIN_BUILD_ACTIVITIES QCommandLineOption noActivitiesOption(QStringLiteral("no-kactivities"), i18n("Disable KActivities integration.")); parser.addOption(noActivitiesOption); #endif 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 if (parser.isSet(noActivitiesOption)) { 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)); } #if HAVE_INPUT KWin::Application::setUseLibinput(parser.isSet(libinputOption)); #endif QString pluginName; QSize initialWindowSize; QByteArray deviceIdentifier; int outputCount = 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; } initialWindowSize = QSize(width, height); } if (hasOutputCountOption) { bool ok = false; const int count = parser.value(outputCountOption).toInt(&ok); if (ok) { outputCount = qMax(1, count); } } if (hasWindowedOption && parser.isSet(windowedOption)) { if (hasX11Option && parser.isSet(x11DisplayOption)) { deviceIdentifier = parser.value(x11DisplayOption).toUtf8(); } else if (!(hasWaylandOption && parser.isSet(waylandDisplayOption))) { deviceIdentifier = qgetenv("DISPLAY"); } if (!deviceIdentifier.isEmpty()) { pluginName = KWin::s_x11Plugin; } else if (hasWaylandOption) { if (parser.isSet(waylandDisplayOption)) { deviceIdentifier = parser.value(waylandDisplayOption).toUtf8(); } else { deviceIdentifier = qgetenv("WAYLAND_DISPLAY"); } if (!deviceIdentifier.isEmpty()) { 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; } server->init(parser.value(waylandSocketOption).toUtf8(), flags); 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()->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/plugins/qpa/integration.cpp b/plugins/qpa/integration.cpp index 569b9c77b..41b47eeee 100644 --- a/plugins/qpa/integration.cpp +++ b/plugins/qpa/integration.cpp @@ -1,265 +1,300 @@ /******************************************************************** 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 . *********************************************************************/ #define WL_EGL_PLATFORM 1 #include "integration.h" #include "platform.h" #include "backingstore.h" #include "nativeinterface.h" #include "platformcontextwayland.h" #include "screen.h" #include "sharingplatformcontext.h" #include "window.h" +#include "../../virtualkeyboard.h" #include "../../main.h" #include "../../wayland_server.h" #include #include #include #include #include #include #include #include #include #include +#include +#include +#include #include #include #include #include namespace KWin { namespace QPA { Integration::Integration() : QObject() , QPlatformIntegration() , m_fontDb(new QGenericUnixFontDatabase()) , m_nativeInterface(new NativeInterface(this)) + , m_inputContext() { } Integration::~Integration() = default; bool Integration::hasCapability(Capability cap) const { switch (cap) { case ThreadedPixmaps: return true; case OpenGL: return true; case ThreadedOpenGL: return false; case BufferQueueingOpenGL: return false; case MultipleWindows: case NonFullScreenWindows: return true; case RasterGLSurface: return false; default: return QPlatformIntegration::hasCapability(cap); } } void Integration::initialize() { // TODO: start initialize Wayland once the internal Wayland connection is created connect(kwinApp(), &Application::screensCreated, this, &Integration::initializeWayland, Qt::QueuedConnection); QPlatformIntegration::initialize(); m_dummyScreen = new Screen(nullptr); screenAdded(m_dummyScreen); + m_inputContext.reset(QPlatformInputContextFactory::create(QStringLiteral("qtvirtualkeyboard"))); + qunsetenv("QT_IM_MODULE"); + connect(qApp, &QGuiApplication::focusObjectChanged, this, + [this] { + if (VirtualKeyboard::self() && qApp->focusObject() != VirtualKeyboard::self()) { + m_inputContext->setFocusObject(VirtualKeyboard::self()); + } + } + ); + connect(kwinApp(), &Application::workspaceCreated, this, + [this] { + if (VirtualKeyboard::self()) { + m_inputContext->setFocusObject(VirtualKeyboard::self()); + } + } + ); + connect(qApp->inputMethod(), &QInputMethod::visibleChanged, this, + [this] { + if (qApp->inputMethod()->isVisible()) { + if (QWindow *w = VirtualKeyboard::self()->inputPanel()) { + QWindowSystemInterface::handleWindowActivated(w, Qt::ActiveWindowFocusReason); + } + } + } + ); } QAbstractEventDispatcher *Integration::createEventDispatcher() const { return new QUnixEventDispatcherQPA; } QPlatformBackingStore *Integration::createPlatformBackingStore(QWindow *window) const { auto registry = waylandServer()->internalClientRegistry(); const auto shm = registry->interface(KWayland::Client::Registry::Interface::Shm); if (shm.name == 0u) { return nullptr; } return new BackingStore(window, registry->createShmPool(shm.name, shm.version, window)); } QPlatformWindow *Integration::createPlatformWindow(QWindow *window) const { auto c = compositor(); auto s = shell(); if (!s || !c) { return new QPlatformWindow(window); } else { // don't set window as parent, cause infinite recursion in PlasmaQuick::Dialog auto surface = c->createSurface(c); return new Window(window, surface, s->createSurface(surface, surface), this); } } QPlatformFontDatabase *Integration::fontDatabase() const { return m_fontDb; } QPlatformTheme *Integration::createPlatformTheme(const QString &name) const { return QGenericUnixTheme::createUnixTheme(name); } QStringList Integration::themeNames() const { if (qEnvironmentVariableIsSet("KDE_FULL_SESSION")) { return QStringList({QStringLiteral("kde")}); } return QStringList({QLatin1String(QGenericUnixTheme::name)}); } QPlatformNativeInterface *Integration::nativeInterface() const { return m_nativeInterface; } QPlatformOpenGLContext *Integration::createPlatformOpenGLContext(QOpenGLContext *context) const { if (kwinApp()->platform()->supportsQpaContext()) { return new SharingPlatformContext(context, const_cast(this)); } if (m_eglDisplay == EGL_NO_DISPLAY) { const_cast(this)->initEgl(); } if (m_eglDisplay == EGL_NO_DISPLAY) { return nullptr; } return new PlatformContextWayland(context, const_cast(this)); } void Integration::initializeWayland() { if (m_registry) { return; } using namespace KWayland::Client; auto setupRegistry = [this] { m_registry = waylandServer()->internalClientRegistry(); connect(m_registry, &Registry::outputAnnounced, this, &Integration::createWaylandOutput); const auto outputs = m_registry->interfaces(Registry::Interface::Output); for (const auto &o : outputs) { createWaylandOutput(o.name, o.version); } }; if (waylandServer()->internalClientRegistry()) { setupRegistry(); } else { connect(waylandServer()->internalClientConection(), &ConnectionThread::connected, this, setupRegistry, Qt::QueuedConnection); } } void Integration::createWaylandOutput(quint32 name, quint32 version) { if (m_dummyScreen) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) destroyScreen(m_dummyScreen); #endif m_dummyScreen = nullptr; } using namespace KWayland::Client; auto o = m_registry->createOutput(name, version, this); connect(o, &Output::changed, this, [this, o] { disconnect(o, &Output::changed, nullptr, nullptr); // TODO: handle screen removal screenAdded(new Screen(o)); } ); waylandServer()->internalClientConection()->flush(); } KWayland::Client::Compositor *Integration::compositor() const { if (!m_compositor) { using namespace KWayland::Client; auto registry = waylandServer()->internalClientRegistry(); const auto c = registry->interface(Registry::Interface::Compositor); if (c.name != 0u) { const_cast(this)->m_compositor = registry->createCompositor(c.name, c.version, registry); } } return m_compositor; } KWayland::Client::Shell *Integration::shell() const { if (!m_shell) { using namespace KWayland::Client; auto registry = waylandServer()->internalClientRegistry(); const auto s = registry->interface(Registry::Interface::Shell); if (s.name != 0u) { const_cast(this)->m_shell = registry->createShell(s.name, s.version, registry); } } return m_shell; } EGLDisplay Integration::eglDisplay() const { return m_eglDisplay; } void Integration::initEgl() { Q_ASSERT(m_eglDisplay == EGL_NO_DISPLAY); // This variant uses Wayland as the EGL platform qputenv("EGL_PLATFORM", "wayland"); m_eglDisplay = eglGetDisplay(waylandServer()->internalClientConection()->display()); if (m_eglDisplay == EGL_NO_DISPLAY) { return; } // call eglInitialize in a thread to not block QFuture future = QtConcurrent::run([this] () -> bool { EGLint major, minor; if (eglInitialize(m_eglDisplay, &major, &minor) == EGL_FALSE) { return false; } EGLint error = eglGetError(); if (error != EGL_SUCCESS) { return false; } return true; }); // TODO: make this better while (!future.isFinished()) { waylandServer()->internalClientConection()->flush(); QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); } if (!future.result()) { eglTerminate(m_eglDisplay); m_eglDisplay = EGL_NO_DISPLAY; } } +QPlatformInputContext *Integration::inputContext() const +{ + return m_inputContext.data(); +} + } } diff --git a/plugins/qpa/integration.h b/plugins/qpa/integration.h index 84b5e60da..3364bbdaf 100644 --- a/plugins/qpa/integration.h +++ b/plugins/qpa/integration.h @@ -1,85 +1,87 @@ /******************************************************************** 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_QPA_INTEGRATION_H #define KWIN_QPA_INTEGRATION_H #include #include #include #include namespace KWayland { namespace Client { class Registry; class Compositor; class Shell; } } namespace KWin { namespace QPA { class Screen; class Integration : public QObject, public QPlatformIntegration { Q_OBJECT public: explicit Integration(); virtual ~Integration(); bool hasCapability(Capability cap) const override; QPlatformWindow *createPlatformWindow(QWindow *window) const override; QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const override; QAbstractEventDispatcher *createEventDispatcher() const override; QPlatformFontDatabase *fontDatabase() const override; QStringList themeNames() const override; QPlatformTheme *createPlatformTheme(const QString &name) const override; QPlatformNativeInterface *nativeInterface() const override; QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const override; void initialize() override; + QPlatformInputContext *inputContext() const override; KWayland::Client::Compositor *compositor() const; EGLDisplay eglDisplay() const; private: void initializeWayland(); void createWaylandOutput(quint32 name, quint32 version); void initEgl(); KWayland::Client::Shell *shell() const; QPlatformFontDatabase *m_fontDb; QPlatformNativeInterface *m_nativeInterface; KWayland::Client::Registry *m_registry = nullptr; KWayland::Client::Compositor *m_compositor = nullptr; KWayland::Client::Shell *m_shell = nullptr; EGLDisplay m_eglDisplay = EGL_NO_DISPLAY; Screen *m_dummyScreen = nullptr; + QScopedPointer m_inputContext; }; } } #endif diff --git a/qml/CMakeLists.txt b/qml/CMakeLists.txt index 15aeae1f9..4faff9151 100644 --- a/qml/CMakeLists.txt +++ b/qml/CMakeLists.txt @@ -1 +1,2 @@ install( DIRECTORY outline/plasma DESTINATION ${DATA_INSTALL_DIR}/${KWIN_NAME}/outline ) +install( DIRECTORY virtualkeyboard DESTINATION ${DATA_INSTALL_DIR}/${KWIN_NAME} ) diff --git a/qml/virtualkeyboard/main-enterprise.qml b/qml/virtualkeyboard/main-enterprise.qml new file mode 100644 index 000000000..b744cadd0 --- /dev/null +++ b/qml/virtualkeyboard/main-enterprise.qml @@ -0,0 +1,32 @@ +/******************************************************************** + 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 . +*********************************************************************/ +import QtQuick 2.0 +import QtQuick.Enterprise.VirtualKeyboard 2.0 + +Item { + id: window + InputPanel { + id: inputPanel + objectName: "inputPanel" + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + } +} diff --git a/qml/virtualkeyboard/main.qml b/qml/virtualkeyboard/main.qml new file mode 100644 index 000000000..e8af4b8f6 --- /dev/null +++ b/qml/virtualkeyboard/main.qml @@ -0,0 +1,32 @@ +/******************************************************************** + 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 . +*********************************************************************/ +import QtQuick 2.0 +import QtQuick.VirtualKeyboard 2.1 + +Item { + id: window + InputPanel { + id: inputPanel + objectName: "inputPanel" + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + } +} diff --git a/shell_client.cpp b/shell_client.cpp index bc164e2a6..55d301c98 100644 --- a/shell_client.cpp +++ b/shell_client.cpp @@ -1,1009 +1,1012 @@ /******************************************************************** 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 "shell_client.h" #include "composite.h" #include "cursor.h" #include "deleted.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "virtualdesktops.h" #include "workspace.h" #include "decorations/decorationbridge.h" #include "decorations/decoratedclient.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KWayland::Server; namespace KWin { ShellClient::ShellClient(ShellSurfaceInterface *surface) : AbstractClient() , m_shellSurface(surface) , m_internal(surface->client() == waylandServer()->internalConnection()) { setSurface(surface->surface()); init(); } ShellClient::~ShellClient() = default; void ShellClient::init() { findInternalWindow(); createWindowId(); setupCompositing(); SurfaceInterface *s = surface(); Q_ASSERT(s); if (s->buffer()) { setReadyForPainting(); setupWindowManagementInterface(); m_unmapped = false; m_clientSize = s->buffer()->size(); } else { ready_for_painting = false; } if (m_internalWindow) { updateInternalWindowGeometry(); setOnAllDesktops(true); } else { doSetGeometry(QRect(QPoint(0, 0), m_clientSize)); setDesktop(VirtualDesktopManager::self()->current()); } if (waylandServer()->inputMethodConnection() == s->client()) { m_windowType = NET::OnScreenDisplay; } connect(s, &SurfaceInterface::sizeChanged, this, [this] { m_clientSize = surface()->buffer()->size(); doSetGeometry(QRect(geom.topLeft(), m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); discardWindowPixmap(); } ); connect(s, &SurfaceInterface::unmapped, this, &ShellClient::unmap); connect(s, &SurfaceInterface::destroyed, this, &ShellClient::destroyClient); if (m_shellSurface) { connect(m_shellSurface, &ShellSurfaceInterface::destroyed, this, &ShellClient::destroyClient); connect(m_shellSurface, &ShellSurfaceInterface::titleChanged, this, &ShellClient::captionChanged); connect(m_shellSurface, &ShellSurfaceInterface::fullscreenChanged, this, &ShellClient::clientFullScreenChanged); connect(m_shellSurface, &ShellSurfaceInterface::maximizedChanged, this, [this] (bool maximized) { maximize(maximized ? MaximizeFull : MaximizeRestore); } ); connect(m_shellSurface, &ShellSurfaceInterface::windowClassChanged, this, &ShellClient::updateIcon); setResourceClass(m_shellSurface->windowClass()); connect(m_shellSurface, &ShellSurfaceInterface::windowClassChanged, this, [this] { setResourceClass(m_shellSurface->windowClass()); } ); connect(m_shellSurface, &ShellSurfaceInterface::transientForChanged, this, &ShellClient::setTransient); connect(m_shellSurface, &ShellSurfaceInterface::moveRequested, this, [this] { // TODO: check the seat and serial performMouseCommand(Options::MouseMove, Cursor::pos()); } ); connect(m_shellSurface, &ShellSurfaceInterface::resizeRequested, this, [this] (SeatInterface *seat, quint32 serial, Qt::Edges edges) { // TODO: check the seat and serial Q_UNUSED(seat) Q_UNUSED(serial) if (!isResizable() || isShade()) { return; } if (isMoveResize()) { finishMoveResize(false); } setMoveResizePointerButtonDown(true); setMoveOffset(Cursor::pos() - pos()); // map from global setInvertedMoveOffset(rect().bottomRight() - moveOffset()); setUnrestrictedMoveResize(false); auto toPosition = [edges] { Position pos = PositionCenter; if (edges.testFlag(Qt::TopEdge)) { pos = PositionTop; } else if (edges.testFlag(Qt::BottomEdge)) { pos = PositionBottom; } if (edges.testFlag(Qt::LeftEdge)) { pos = Position(pos | PositionLeft); } else if (edges.testFlag(Qt::RightEdge)) { pos = Position(pos | PositionRight); } return pos; }; setMoveResizePointerMode(toPosition()); if (!startMoveResize()) setMoveResizePointerButtonDown(false); updateCursor(); } ); } updateIcon(); // setup shadow integration getShadow(); connect(s, &SurfaceInterface::shadowChanged, this, &Toplevel::getShadow); setTransient(); // check whether we have a ServerSideDecoration if (ServerSideDecorationInterface *deco = ServerSideDecorationInterface::get(s)) { installServerSideDecoration(deco); } updateColorScheme(QString()); } void ShellClient::destroyClient() { m_closing = true; Deleted *del = nullptr; if (workspace()) { del = Deleted::create(this); } emit windowClosed(this, del); destroyDecoration(); if (workspace()) { StackingUpdatesBlocker blocker(workspace()); if (transientFor()) { transientFor()->removeTransient(this); } for (auto it = transients().constBegin(); it != transients().constEnd();) { if ((*it)->transientFor() == this) { removeTransient(*it); it = transients().constBegin(); // restart, just in case something more has changed with the list } else { ++it; } } } waylandServer()->removeClient(this); if (del) { del->unrefWindow(); } m_shellSurface = nullptr; deleteClient(this); } void ShellClient::deleteClient(ShellClient *c) { delete c; } QStringList ShellClient::activities() const { // TODO: implement return QStringList(); } QPoint ShellClient::clientContentPos() const { return -1 * clientPos(); } QSize ShellClient::clientSize() const { // TODO: connect for changes return m_clientSize; } void ShellClient::debug(QDebug &stream) const { // TODO: implement Q_UNUSED(stream) } Layer ShellClient::layerForDock() const { if (m_plasmaShellSurface) { switch (m_plasmaShellSurface->panelBehavior()) { case PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover: return NormalLayer; case PlasmaShellSurfaceInterface::PanelBehavior::AutoHide: return AboveLayer; case PlasmaShellSurfaceInterface::PanelBehavior::WindowsGoBelow: case PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible: return DockLayer; default: Q_UNREACHABLE(); break; } } return AbstractClient::layerForDock(); } bool ShellClient::shouldUnredirect() const { // TODO: unredirect for fullscreen return false; } QRect ShellClient::transparentRect() const { // TODO: implement return QRect(); } NET::WindowType ShellClient::windowType(bool direct, int supported_types) const { // TODO: implement Q_UNUSED(direct) Q_UNUSED(supported_types) return m_windowType; } double ShellClient::opacity() const { return m_opacity; } void ShellClient::setOpacity(double opacity) { const qreal newOpacity = qBound(0.0, opacity, 1.0); if (newOpacity == m_opacity) { return; } const qreal oldOpacity = m_opacity; m_opacity = newOpacity; addRepaintFull(); emit opacityChanged(this, oldOpacity); } void ShellClient::addDamage(const QRegion &damage) { auto s = surface(); if (s->buffer()->size().isValid()) { m_clientSize = s->buffer()->size(); QPoint position = geom.topLeft(); if (m_positionAfterResize.isValid()) { addLayerRepaint(geometry()); position = m_positionAfterResize.point(); m_positionAfterResize.clear(); } doSetGeometry(QRect(position, m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); } markAsMapped(); setDepth(s->buffer()->hasAlphaChannel() ? 32 : 24); repaints_region += damage.translated(clientPos()); Toplevel::addDamage(damage); } void ShellClient::setInternalFramebufferObject(const QSharedPointer &fbo) { if (fbo.isNull()) { unmap(); return; } markAsMapped(); m_clientSize = fbo->size(); doSetGeometry(QRect(geom.topLeft(), m_clientSize)); Toplevel::setInternalFramebufferObject(fbo); Toplevel::addDamage(QRegion(0, 0, width(), height())); } void ShellClient::markAsMapped() { if (!m_unmapped) { return; } m_unmapped = false; setReadyForPainting(); setupWindowManagementInterface(); } void ShellClient::createDecoration(const QRect &oldGeom) { KDecoration2::Decoration *decoration = Decoration::DecorationBridge::self()->createDecoration(this); if (decoration) { QMetaObject::invokeMethod(decoration, "update", Qt::QueuedConnection); connect(decoration, &KDecoration2::Decoration::shadowChanged, this, &Toplevel::getShadow); connect(decoration, &KDecoration2::Decoration::bordersChanged, this, [this]() { GeometryUpdatesBlocker blocker(this); QRect oldgeom = geometry(); if (!isShade()) checkWorkspacePosition(oldgeom); emit geometryShapeChanged(this, oldgeom); } ); } setDecoration(decoration); emit geometryShapeChanged(this, oldGeom); } void ShellClient::updateDecoration(bool check_workspace_pos, bool force) { if (!force && ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder()))) return; QRect oldgeom = geometry(); QRect oldClientGeom = oldgeom.adjusted(borderLeft(), borderTop(), -borderRight(), -borderBottom()); blockGeometryUpdates(true); if (force) destroyDecoration(); if (!noBorder()) { createDecoration(oldgeom); } else destroyDecoration(); if (m_serverDecoration && isDecorated()) { m_serverDecoration->setMode(KWayland::Server::ServerSideDecorationManagerInterface::Mode::Server); } getShadow(); if (check_workspace_pos) checkWorkspacePosition(oldgeom, -2, oldClientGeom); blockGeometryUpdates(false); } void ShellClient::setGeometry(int x, int y, int w, int h, ForceGeometry_t force) { Q_UNUSED(force) // TODO: better merge with Client's implementation if (QSize(w, h) == geom.size()) { // size didn't change, update directly doSetGeometry(QRect(x, y, w, h)); } else { // size did change, Client needs to provide a new buffer requestGeometry(QRect(x, y, w, h)); } } void ShellClient::doSetGeometry(const QRect &rect) { if (geom == rect) { return; } if (!m_unmapped) { addWorkspaceRepaint(visibleRect()); } const QRect old = geom; geom = rect; if (m_unmapped && m_geomMaximizeRestore.isEmpty() && !geom.isEmpty()) { // use first valid geometry as restore geometry // TODO: needs to interact with placing. The first valid geometry should be the placed one m_geomMaximizeRestore = geom; } if (!m_unmapped) { addWorkspaceRepaint(visibleRect()); } triggerDecorationRepaint(); emit geometryShapeChanged(this, old); } QByteArray ShellClient::windowRole() const { return QByteArray(); } bool ShellClient::belongsToSameApplication(const AbstractClient *other, bool active_hack) const { Q_UNUSED(active_hack) if (auto s = other->surface()) { return s->client() == surface()->client(); } return false; } void ShellClient::blockActivityUpdates(bool b) { Q_UNUSED(b) } QString ShellClient::caption(bool full, bool stripped) const { Q_UNUSED(full) Q_UNUSED(stripped) if (m_shellSurface) { return m_shellSurface->title(); } return QString(); } void ShellClient::closeWindow() { if (m_qtExtendedSurface && isCloseable()) { m_qtExtendedSurface->close(); } } AbstractClient *ShellClient::findModal(bool allow_itself) { Q_UNUSED(allow_itself) return nullptr; } bool ShellClient::isCloseable() const { if (m_windowType == NET::Desktop || m_windowType == NET::Dock) { return false; } return m_qtExtendedSurface ? true : false; } bool ShellClient::isFullScreenable() const { return false; } bool ShellClient::isFullScreen() const { return m_fullScreen; } bool ShellClient::isMaximizable() const { return true; } bool ShellClient::isMinimizable() const { return (!m_plasmaShellSurface || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal); } QRect ShellClient::iconGeometry() const { if (!windowManagementInterface()) { // window management interface is only available if the surface is mapped return QRect(); } int minDistance = INT_MAX; ShellClient *candidatePanel = nullptr; QRect candidateGeom; for (auto i = windowManagementInterface()->minimizedGeometries().constBegin(), end = windowManagementInterface()->minimizedGeometries().constEnd(); i != end; ++i) { ShellClient *client = waylandServer()->findClient(i.key()); if (!client) { continue; } const int distance = QPoint(client->pos() - pos()).manhattanLength(); if (distance < minDistance) { minDistance = distance; candidatePanel = client; candidateGeom = i.value(); } } if (!candidatePanel) { return QRect(); } return candidateGeom.translated(candidatePanel->pos()); } bool ShellClient::isMovable() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; } return true; } bool ShellClient::isMovableAcrossScreens() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; } return true; } bool ShellClient::isResizable() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; } return true; } bool ShellClient::isShown(bool shaded_is_shown) const { Q_UNUSED(shaded_is_shown) return !m_closing && !m_unmapped; } void ShellClient::hideClient(bool hide) { Q_UNUSED(hide) } void ShellClient::changeMaximize(bool horizontal, bool vertical, bool adjust) { StackingUpdatesBlocker blocker(workspace()); // 'adjust == true' means to update the size only, e.g. after changing workspace size if (!adjust) { if (vertical) m_maximizeMode = MaximizeMode(m_maximizeMode ^ MaximizeVertical); if (horizontal) m_maximizeMode = MaximizeMode(m_maximizeMode ^ MaximizeHorizontal); } // TODO: check rules if (m_maximizeMode == MaximizeFull) { m_geomMaximizeRestore = geometry(); requestGeometry(workspace()->clientArea(MaximizeArea, this)); workspace()->raiseClient(this); } else { if (m_geomMaximizeRestore.isValid()) { requestGeometry(m_geomMaximizeRestore); } else { requestGeometry(workspace()->clientArea(PlacementArea, this)); } } // TODO: add more checks as in Client } MaximizeMode ShellClient::maximizeMode() const { return m_maximizeMode; } bool ShellClient::noBorder() const { if (isInternal()) { return true; } if (m_serverDecoration) { if (m_serverDecoration->mode() == ServerSideDecorationManagerInterface::Mode::Server) { return m_userNoBorder; } } return true; } const WindowRules *ShellClient::rules() const { static WindowRules s_rules; return &s_rules; } void ShellClient::setFullScreen(bool set, bool user) { Q_UNUSED(set) Q_UNUSED(user) } void ShellClient::setNoBorder(bool set) { if (!userCanSetNoBorder()) { return; } set = rules()->checkNoBorder(set); if (m_userNoBorder == set) { return; } m_userNoBorder = set; updateDecoration(true, false); updateWindowRules(Rules::NoBorder); } void ShellClient::setOnAllActivities(bool set) { Q_UNUSED(set) } void ShellClient::setShortcut(const QString &cut) { Q_UNUSED(cut) } const QKeySequence &ShellClient::shortcut() const { static QKeySequence seq; return seq; } void ShellClient::takeFocus() { if (rules()->checkAcceptFocus(wantsInput())) { setActive(true); } bool breakShowingDesktop = !keepAbove() && !isOnScreenDisplay(); if (breakShowingDesktop) { // check that it doesn't belong to the desktop const auto &clients = waylandServer()->clients(); for (auto c: clients) { if (!belongsToSameApplication(c, false)) { continue; } if (c->isDesktop()) { breakShowingDesktop = false; break; } } } if (breakShowingDesktop) workspace()->setShowingDesktop(false); } void ShellClient::doSetActive() { StackingUpdatesBlocker blocker(workspace()); workspace()->focusToNull(); } void ShellClient::updateWindowRules(Rules::Types selection) { Q_UNUSED(selection) } bool ShellClient::userCanSetFullScreen() const { return false; } bool ShellClient::userCanSetNoBorder() const { if (m_serverDecoration && m_serverDecoration->mode() == ServerSideDecorationManagerInterface::Mode::Server) { return !isFullScreen() && !isShade() && !tabGroup(); } return false; } bool ShellClient::wantsInput() const { return rules()->checkAcceptFocus(acceptsFocus()); } bool ShellClient::acceptsFocus() const { if (isInternal()) { return false; } if (waylandServer()->inputMethodConnection() == surface()->client()) { return false; } if (m_shellSurface) { if (m_shellSurface->isPopup()) { return false; } // if the window is not visible it doesn't get input return m_shellSurface->acceptsKeyboardFocus() && isShown(true); } return false; } void ShellClient::createWindowId() { if (m_internalWindow) { m_windowId = m_internalWindow->winId(); } else { m_windowId = waylandServer()->createWindowId(surface()); } } void ShellClient::findInternalWindow() { if (surface()->client() != waylandServer()->internalConnection()) { return; } const QWindowList windows = kwinApp()->topLevelWindows(); for (QWindow *w: windows) { auto s = KWayland::Client::Surface::fromWindow(w); if (!s) { continue; } if (s->id() != surface()->id()) { continue; } m_internalWindow = w; connect(m_internalWindow, &QWindow::xChanged, this, &ShellClient::updateInternalWindowGeometry); connect(m_internalWindow, &QWindow::yChanged, this, &ShellClient::updateInternalWindowGeometry); connect(m_internalWindow, &QWindow::destroyed, this, [this] { m_internalWindow = nullptr; }); // Try reading the window type from the QWindow. PlasmaCore.Dialog provides a dynamic type property // let's check whether it exists, if it does it's our window type const QVariant windowType = m_internalWindow->property("type"); if (!windowType.isNull()) { m_windowType = static_cast(windowType.toInt()); } return; } } void ShellClient::updateInternalWindowGeometry() { if (!m_internalWindow) { return; } doSetGeometry(m_internalWindow->geometry()); } bool ShellClient::isInternal() const { return m_internal; } bool ShellClient::isLockScreen() const { return surface()->client() == waylandServer()->screenLockerClientConnection(); } bool ShellClient::isInputMethod() const { + if (m_internal && m_internalWindow) { + return m_internalWindow->property("__kwin_input_method").toBool(); + } return surface()->client() == waylandServer()->inputMethodConnection(); } void ShellClient::requestGeometry(const QRect &rect) { m_positionAfterResize.setPoint(rect.topLeft()); if (m_shellSurface) { m_shellSurface->requestSize(rect.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom())); } } void ShellClient::clientFullScreenChanged(bool fullScreen) { StackingUpdatesBlocker blocker(workspace()); workspace()->updateClientLayer(this); // active fullscreens get different layer if (fullScreen) { m_geomFsRestore = geometry(); requestGeometry(workspace()->clientArea(FullScreenArea, this)); workspace()->raiseClient(this); } else { if (m_geomFsRestore.isValid()) { requestGeometry(m_geomFsRestore); } else { requestGeometry(workspace()->clientArea(MaximizeArea, this)); } } if (m_fullScreen != fullScreen) { m_fullScreen = fullScreen; emit fullScreenChanged(); } } void ShellClient::resizeWithChecks(int w, int h, ForceGeometry_t force) { Q_UNUSED(force) QRect area = workspace()->clientArea(WorkArea, this); // don't allow growing larger than workarea if (w > area.width()) { w = area.width(); } if (h > area.height()) { h = area.height(); } if (m_shellSurface) { m_shellSurface->requestSize(QSize(w, h)); } } void ShellClient::unmap() { m_unmapped = true; ready_for_painting = false; destroyWindowManagementInterface(); if (Workspace::self()) { addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); } emit windowHidden(this); } void ShellClient::installPlasmaShellSurface(PlasmaShellSurfaceInterface *surface) { m_plasmaShellSurface = surface; auto updatePosition = [this, surface] { doSetGeometry(QRect(surface->position(), m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); }; auto updateRole = [this, surface] { NET::WindowType type = NET::Unknown; switch (surface->role()) { case PlasmaShellSurfaceInterface::Role::Desktop: type = NET::Desktop; break; case PlasmaShellSurfaceInterface::Role::Panel: type = NET::Dock; break; case PlasmaShellSurfaceInterface::Role::OnScreenDisplay: type = NET::OnScreenDisplay; break; case PlasmaShellSurfaceInterface::Role::Normal: default: type = NET::Normal; break; } if (type != m_windowType) { m_windowType = type; workspace()->updateClientArea(); } }; connect(surface, &PlasmaShellSurfaceInterface::positionChanged, this, updatePosition); connect(surface, &PlasmaShellSurfaceInterface::roleChanged, this, updateRole); connect(surface, &PlasmaShellSurfaceInterface::panelBehaviorChanged, this, [] { workspace()->updateClientArea(); } ); updatePosition(); updateRole(); setSkipTaskbar(surface->skipTaskbar()); connect(surface, &PlasmaShellSurfaceInterface::skipTaskbarChanged, this, [this] { setSkipTaskbar(m_plasmaShellSurface->skipTaskbar()); }); } bool ShellClient::isInitialPositionSet() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->isPositionSet(); } return false; } void ShellClient::installQtExtendedSurface(QtExtendedSurfaceInterface *surface) { m_qtExtendedSurface = surface; connect(m_qtExtendedSurface.data(), &QtExtendedSurfaceInterface::raiseRequested, this, [this]() { workspace()->raiseClientRequest(this); }); connect(m_qtExtendedSurface.data(), &QtExtendedSurfaceInterface::lowerRequested, this, [this]() { workspace()->lowerClientRequest(this); }); } bool ShellClient::hasStrut() const { if (!isShown(true)) { return false; } if (!m_plasmaShellSurface) { return false; } if (m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) { return false; } return m_plasmaShellSurface->panelBehavior() != PlasmaShellSurfaceInterface::PanelBehavior::WindowsGoBelow; } void ShellClient::updateIcon() { QString desktopFile; if (m_shellSurface) { desktopFile = QString::fromUtf8(m_shellSurface->windowClass()); } if (desktopFile.isEmpty()) { setIcon(QIcon()); } if (!desktopFile.endsWith(QLatin1String(".desktop"))) { desktopFile.append(QLatin1String(".desktop")); } KDesktopFile df(desktopFile); setIcon(QIcon::fromTheme(df.readIcon())); } bool ShellClient::isTransient() const { return m_transient; } void ShellClient::setTransient() { SurfaceInterface *s = nullptr; if (m_shellSurface) { s = m_shellSurface->transientFor().data(); } auto t = waylandServer()->findClient(s); if (t != transientFor()) { // remove from main client if (transientFor()) transientFor()->removeTransient(this); setTransientFor(t); if (t) { t->addTransient(this); } } m_transient = (s != nullptr); } bool ShellClient::hasTransientPlacementHint() const { return isTransient() && transientFor() != nullptr; } QPoint ShellClient::transientPlacementHint() const { if (m_shellSurface) { return m_shellSurface->transientOffset(); } return QPoint(); } bool ShellClient::isWaitingForMoveResizeSync() const { return m_positionAfterResize.isValid(); } void ShellClient::doResizeSync() { requestGeometry(moveResizeGeometry()); } QMatrix4x4 ShellClient::inputTransformation() const { QMatrix4x4 m = Toplevel::inputTransformation(); m.translate(-borderLeft(), -borderTop()); return m; } void ShellClient::installServerSideDecoration(KWayland::Server::ServerSideDecorationInterface *deco) { if (m_serverDecoration == deco) { return; } m_serverDecoration = deco; connect(m_serverDecoration, &ServerSideDecorationInterface::destroyed, this, [this] { m_serverDecoration = nullptr; if (m_closing || !Workspace::self()) { return; } if (!m_unmapped) { // maybe delay to next event cycle in case the ShellClient is getting destroyed, too updateDecoration(true); } } ); if (!m_unmapped) { updateDecoration(true); } connect(m_serverDecoration, &ServerSideDecorationInterface::modeRequested, this, [this] (ServerSideDecorationManagerInterface::Mode mode) { const bool changed = mode != m_serverDecoration->mode(); // always acknowledge the requested mode m_serverDecoration->setMode(mode); if (changed && !m_unmapped) { updateDecoration(false); } } ); } } diff --git a/tests/inputmethodstest.qml b/tests/inputmethodstest.qml new file mode 100644 index 000000000..85d853bf0 --- /dev/null +++ b/tests/inputmethodstest.qml @@ -0,0 +1,84 @@ +/******************************************************************** + 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 . +*********************************************************************/ +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 + +GridLayout { + columns: 2 + Label { + text: "Normal:" + } + TextField { + } + Label { + text: "Digits:" + } + TextField { + inputMethodHints: Qt.ImhDigitsOnly + } + Label { + text: "Numbers:" + } + TextField { + inputMethodHints: Qt.ImhFormattedNumbersOnly + } + Label { + text: "Uppercase:" + } + TextField { + inputMethodHints: Qt.ImhUppercaseOnly + } + Label { + text: "Lowercase:" + } + TextField { + inputMethodHints: Qt.ImhLowercaseOnly + } + Label { + text: "Phone:" + } + TextField { + inputMethodHints: Qt.ImhDialableCharactersOnly + } + Label { + text: "Email:" + } + TextField { + inputMethodHints: Qt.ImhEmailCharactersOnly + } + Label { + text: "Url:" + } + TextField { + inputMethodHints: Qt.ImhUrlCharactersOnly + } + Label { + text: "Date:" + } + TextField { + inputMethodHints: Qt.ImhDate + } + Label { + text: "Time:" + } + TextField { + inputMethodHints: Qt.ImhTime + } +} diff --git a/virtualkeyboard.cpp b/virtualkeyboard.cpp new file mode 100644 index 000000000..f62089c20 --- /dev/null +++ b/virtualkeyboard.cpp @@ -0,0 +1,436 @@ +/******************************************************************** + 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 "virtualkeyboard.h" +#include "utils.h" +#include "screens.h" +#include "wayland_server.h" +#include "workspace.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace KWayland::Server; + +namespace KWin +{ + +KWIN_SINGLETON_FACTORY(VirtualKeyboard) + +VirtualKeyboard::VirtualKeyboard(QObject *parent) + : QObject(parent) +{ + // this is actually too late. Other processes are started before init, + // so might miss the availability of text input + // but without Workspace we don't have the window listed at all + connect(kwinApp(), &Application::workspaceCreated, this, &VirtualKeyboard::init); +} + +VirtualKeyboard::~VirtualKeyboard() = default; + +void VirtualKeyboard::init() +{ + // TODO: need a shared Qml engine + m_inputWindow.reset(new QQuickView(nullptr)); + m_inputWindow->setGeometry(screens()->geometry(screens()->current())); + m_inputWindow->setResizeMode(QQuickView::SizeRootObjectToView); + m_inputWindow->setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral(KWIN_NAME "/virtualkeyboard/main.qml")))); + if (m_inputWindow->status() != QQuickView::Status::Ready) { + // try enterprise + m_inputWindow->setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral(KWIN_NAME "/virtualkeyboard/main-enterprise.qml")))); + if (m_inputWindow->status() != QQuickView::Status::Ready) { + m_inputWindow.reset(); + return; + } + } + m_inputWindow->setProperty("__kwin_input_method", true); + + if (waylandServer()) { + m_enabled = !waylandServer()->seat()->hasKeyboard(); + connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::hasKeyboardChanged, this, + [this] { + setEnabled(!waylandServer()->seat()->hasKeyboard()); + } + ); + } + + m_sni = new KStatusNotifierItem(QStringLiteral("kwin-virtual-keyboard"), this); + m_sni->setCategory(KStatusNotifierItem::Hardware); + m_sni->setStatus(KStatusNotifierItem::Passive); + m_sni->setTitle(i18n("Virtual Keyboard")); + updateSni(); + connect(m_sni, &KStatusNotifierItem::activateRequested, this, + [this] { + setEnabled(!m_enabled); + } + ); + + if (waylandServer()) { + // we can announce support for the text input interface + auto t = waylandServer()->display()->createTextInputManager(TextInputInterfaceVersion::UnstableV0, waylandServer()->display()); + t->create(); + auto t2 = waylandServer()->display()->createTextInputManager(TextInputInterfaceVersion::UnstableV2, waylandServer()->display()); + t2->create(); + connect(waylandServer()->seat(), &SeatInterface::focusedTextInputChanged, this, + [this] { + disconnect(m_waylandShowConnection); + disconnect(m_waylandHideConnection); + disconnect(m_waylandHintsConnection); + disconnect(m_waylandSurroundingTextConnection); + disconnect(m_waylandResetConnection); + disconnect(m_waylandEnabledConnection); + qApp->inputMethod()->reset(); + if (auto t = waylandServer()->seat()->focusedTextInput()) { + m_waylandShowConnection = connect(t, &TextInputInterface::requestShowInputPanel, this, &VirtualKeyboard::show); + m_waylandHideConnection = connect(t, &TextInputInterface::requestHideInputPanel, this, &VirtualKeyboard::hide); + m_waylandSurroundingTextConnection = connect(t, &TextInputInterface::surroundingTextChanged, this, + [] { + qApp->inputMethod()->update(Qt::ImSurroundingText | Qt::ImCursorPosition | Qt::ImAnchorPosition); + } + ); + m_waylandHintsConnection = connect(t, &TextInputInterface::contentTypeChanged, this, + [] { + qApp->inputMethod()->update(Qt::ImHints); + } + ); + m_waylandResetConnection = connect(t, &TextInputInterface::requestReset, qApp->inputMethod(), &QInputMethod::reset); + m_waylandEnabledConnection = connect(t, &TextInputInterface::enabledChanged, this, + [] { + qApp->inputMethod()->update(Qt::ImQueryAll); + } + ); + // TODO: calculate overlap + t->setInputPanelState(m_inputWindow->isVisible(), QRect(0, 0, 0, 0)); + } else { + m_waylandShowConnection = QMetaObject::Connection(); + m_waylandHideConnection = QMetaObject::Connection(); + m_waylandHintsConnection = QMetaObject::Connection(); + m_waylandSurroundingTextConnection = QMetaObject::Connection(); + m_waylandResetConnection = QMetaObject::Connection(); + m_waylandEnabledConnection = QMetaObject::Connection(); + } + qApp->inputMethod()->update(Qt::ImQueryAll); + } + ); + } + m_inputWindow->installEventFilter(this); + connect(Workspace::self(), &Workspace::destroyed, this, + [this] { + m_inputWindow.reset(); + } + ); + m_inputWindow->setColor(Qt::transparent); + m_inputWindow->setMask(m_inputWindow->rootObject()->childrenRect().toRect()); + connect(m_inputWindow->rootObject(), &QQuickItem::childrenRectChanged, m_inputWindow.data(), + [this] { + if (!m_inputWindow) { + return; + } + m_inputWindow->setMask(m_inputWindow->rootObject()->childrenRect().toRect()); + } + ); + connect(qApp->inputMethod(), &QInputMethod::visibleChanged, m_inputWindow.data(), + [this] { + m_inputWindow->setVisible(qApp->inputMethod()->isVisible()); + if (qApp->inputMethod()->isVisible()) { + m_inputWindow->setMask(m_inputWindow->rootObject()->childrenRect().toRect()); + } + if (waylandServer()) { + if (auto t = waylandServer()->seat()->focusedTextInput()) { + // TODO: calculate overlap + t->setInputPanelState(m_inputWindow->isVisible(), QRect(0, 0, 0, 0)); + } + } + } + ); +} + +void VirtualKeyboard::setEnabled(bool enabled) +{ + if (m_enabled == enabled) { + return; + } + m_enabled = enabled; + qApp->inputMethod()->update(Qt::ImQueryAll); + + updateSni(); + + // send OSD message + QDBusMessage msg = QDBusMessage::createMethodCall( + QStringLiteral("org.kde.plasmashell"), + QStringLiteral("/org/kde/osdService"), + QStringLiteral("org.kde.osdService"), + QStringLiteral("virtualKeyboardEnabledChanged") + ); + msg.setArguments({enabled}); + QDBusConnection::sessionBus().asyncCall(msg); +} + +void VirtualKeyboard::updateSni() +{ + if (!m_sni) { + return; + } + if (m_enabled) { + m_sni->setIconByName(QStringLiteral("input-keyboard-virtual-on")); + m_sni->setToolTipTitle(i18n("Virtual Keyboard is enabled.")); + } else { + m_sni->setIconByName(QStringLiteral("input-keyboard-virtual-off")); + m_sni->setToolTipTitle(i18n("Virtual Keyboard is disabled.")); + } +} + +void VirtualKeyboard::show() +{ + if (m_inputWindow.isNull() || !m_enabled) { + return; + } + m_inputWindow->setGeometry(screens()->geometry(screens()->current())); + qApp->inputMethod()->show(); +} + +void VirtualKeyboard::hide() +{ + if (m_inputWindow.isNull()) { + return; + } + qApp->inputMethod()->hide(); +} + +bool VirtualKeyboard::event(QEvent *e) +{ + if (e->type() == QEvent::InputMethod) { + QInputMethodEvent *event = static_cast(e); + if (m_enabled && waylandServer()) { + bool isPreedit = false; + for (auto attribute : event->attributes()) { + switch (attribute.type) { + case QInputMethodEvent::TextFormat: + case QInputMethodEvent::Cursor: + case QInputMethodEvent::Language: + case QInputMethodEvent::Ruby: + isPreedit = true; + break; + default: + break; + } + } + TextInputInterface *ti = waylandServer()->seat()->focusedTextInput(); + if (ti && ti->isEnabled()) { + if (!isPreedit && event->preeditString().isEmpty() && !event->commitString().isEmpty()) { + ti->commit(event->commitString().toUtf8()); + } else { + ti->preEdit(event->preeditString().toUtf8(), event->commitString().toUtf8()); + } + } + } + } + if (e->type() == QEvent::InputMethodQuery) { + auto event = static_cast(e); + TextInputInterface *ti = nullptr; + if (waylandServer() && m_enabled) { + ti = waylandServer()->seat()->focusedTextInput(); + } + if (event->queries().testFlag(Qt::ImEnabled)) { + event->setValue(Qt::ImEnabled, QVariant(ti != nullptr && ti->isEnabled())); + } + if (event->queries().testFlag(Qt::ImCursorRectangle)) { + // not used by virtual keyboard + } + if (event->queries().testFlag(Qt::ImFont)) { + // not used by virtual keyboard + } + if (event->queries().testFlag(Qt::ImCursorPosition)) { + // the virtual keyboard doesn't send us the cursor position in the preedit + // this would break text input, thus we ignore it + // see https://bugreports.qt.io/browse/QTBUG-53517 +#if 0 + event->setValue(Qt::ImCursorPosition, QString::fromUtf8(ti->surroundingText().left(ti->surroundingTextCursorPosition())).size()); +#else + event->setValue(Qt::ImCursorPosition, 0); +#endif + } + if (event->queries().testFlag(Qt::ImSurroundingText)) { + // the virtual keyboard doesn't send us the cursor position in the preedit + // this would break text input, thus we ignore it + // see https://bugreports.qt.io/browse/QTBUG-53517 +#if 0 + event->setValue(Qt::ImSurroundingText, QString::fromUtf8(ti->surroundingText())); +#else + event->setValue(Qt::ImSurroundingText, QString()); +#endif + } + if (event->queries().testFlag(Qt::ImCurrentSelection)) { + // TODO: should be text between cursor and anchor, but might be dangerous + } + if (event->queries().testFlag(Qt::ImMaximumTextLength)) { + // not used by virtual keyboard + } + if (event->queries().testFlag(Qt::ImAnchorPosition)) { + // not used by virtual keyboard + } + if (event->queries().testFlag(Qt::ImHints)) { + if (ti && ti->isEnabled()) { + Qt::InputMethodHints hints; + const auto contentHints = ti->contentHints(); + if (!contentHints.testFlag(TextInputInterface::ContentHint::AutoCompletion)) { + hints |= Qt::ImhNoPredictiveText; + } + if (contentHints.testFlag(TextInputInterface::ContentHint::AutoCorrection)) { + // no mapping in Qt + } + if (!contentHints.testFlag(TextInputInterface::ContentHint::AutoCapitalization)) { + hints |= Qt::ImhNoAutoUppercase; + } + if (contentHints.testFlag(TextInputInterface::ContentHint::Lowercase)) { + hints |= Qt::ImhPreferLowercase; + } + if (contentHints.testFlag(TextInputInterface::ContentHint::Uppercase)) { + hints |= Qt::ImhPreferUppercase; + } + if (contentHints.testFlag(TextInputInterface::ContentHint::Titlecase)) { + // no mapping in Qt + } + if (contentHints.testFlag(TextInputInterface::ContentHint::HiddenText)) { + hints |= Qt::ImhHiddenText; + } + if (contentHints.testFlag(TextInputInterface::ContentHint::SensitiveData)) { + hints |= Qt::ImhSensitiveData; + } + if (contentHints.testFlag(TextInputInterface::ContentHint::Latin)) { + hints |= Qt::ImhPreferLatin; + } + if (contentHints.testFlag(TextInputInterface::ContentHint::MultiLine)) { + hints |= Qt::ImhMultiLine; + } + switch (ti->contentPurpose()) { + case TextInputInterface::ContentPurpose::Digits: + hints |= Qt::ImhDigitsOnly; + break; + case TextInputInterface::ContentPurpose::Number: + hints |= Qt::ImhFormattedNumbersOnly; + break; + case TextInputInterface::ContentPurpose::Phone: + hints |= Qt::ImhDialableCharactersOnly; + break; + case TextInputInterface::ContentPurpose::Url: + hints |= Qt::ImhUrlCharactersOnly; + case TextInputInterface::ContentPurpose::Email: + hints |= Qt::ImhEmailCharactersOnly; + break; + case TextInputInterface::ContentPurpose::Date: + hints |= Qt::ImhDate; + break; + case TextInputInterface::ContentPurpose::Time: + hints |= Qt::ImhTime; + break; + case TextInputInterface::ContentPurpose::Datetime: + hints |= Qt::ImhDate; + hints |= Qt::ImhTime; + break; + case TextInputInterface::ContentPurpose::Name: + // no mapping in Qt + case TextInputInterface::ContentPurpose::Password: + // no mapping in Qt + case TextInputInterface::ContentPurpose::Terminal: + // no mapping in Qt + case TextInputInterface::ContentPurpose::Normal: + // that's the default + case TextInputInterface::ContentPurpose::Alpha: + // no mapping in Qt + break; + } + event->setValue(Qt::ImHints, QVariant(int(hints))); + } else { + event->setValue(Qt::ImHints, Qt::ImhNone); + } + } + if (event->queries().testFlag(Qt::ImPreferredLanguage)) { + // not used by virtual keyboard + } + if (event->queries().testFlag(Qt::ImPlatformData)) { + // not used by virtual keyboard + } + if (event->queries().testFlag(Qt::ImAbsolutePosition)) { + // not used by virtual keyboard + } + if (event->queries().testFlag(Qt::ImTextBeforeCursor)) { + // not used by virtual keyboard + } + if (event->queries().testFlag(Qt::ImTextAfterCursor)) { + // not used by virtual keyboard + } + event->accept(); + return true; + } + return QObject::event(e); +} + +bool VirtualKeyboard::eventFilter(QObject *o, QEvent *e) +{ + if (o != m_inputWindow.data() || !m_inputWindow->isVisible()) { + return false; + } + if (e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease) { + QKeyEvent *event = static_cast(e); + if (event->nativeScanCode() == 0) { + // this is a key composed by the virtual keyboard - we need to send it to the client + // TODO: proper xkb support in KWindowSystem needed + int sym = 0; + KKeyServer::keyQtToSymX(event->key(), &sym); + if (sym != 0) { + if (waylandServer()) { + auto t = waylandServer()->seat()->focusedTextInput(); + if (t && t->isEnabled()) { + if (e->type() == QEvent::KeyPress) { + t->keysymPressed(sym); + } else if (e->type() == QEvent::KeyRelease) { + t->keysymReleased(sym); + } + } + } + } + return true; + } + } + return false; +} + +QWindow *VirtualKeyboard::inputPanel() const +{ + return m_inputWindow.data(); +} + +} diff --git a/virtualkeyboard.h b/virtualkeyboard.h new file mode 100644 index 000000000..2990be281 --- /dev/null +++ b/virtualkeyboard.h @@ -0,0 +1,68 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2016 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef KWIN_INPUT_METHODS_H +#define KWIN_INPUT_METHODS_H + +#include + +#include +#include + +class QQuickView; +class QWindow; +class KStatusNotifierItem; + +namespace KWin +{ + +class KWIN_EXPORT VirtualKeyboard : public QObject +{ + Q_OBJECT +public: + virtual ~VirtualKeyboard(); + + void init(); + + bool event(QEvent *e) override; + bool eventFilter(QObject *o, QEvent *event) override; + + QWindow *inputPanel() const; + +private: + void show(); + void hide(); + void setEnabled(bool enable); + void updateSni(); + + bool m_enabled = false; + KStatusNotifierItem *m_sni = nullptr; + QScopedPointer m_inputWindow; + QMetaObject::Connection m_waylandShowConnection; + QMetaObject::Connection m_waylandHideConnection; + QMetaObject::Connection m_waylandHintsConnection; + QMetaObject::Connection m_waylandSurroundingTextConnection; + QMetaObject::Connection m_waylandResetConnection; + QMetaObject::Connection m_waylandEnabledConnection; + KWIN_SINGLETON(VirtualKeyboard) +}; + +} + +#endif