diff --git a/abstract_backend.cpp b/abstract_backend.cpp index 59fa327b7..b9f37db88 100644 --- a/abstract_backend.cpp +++ b/abstract_backend.cpp @@ -1,278 +1,278 @@ /******************************************************************** 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 "abstract_backend.h" #include #include "abstract_egl_backend.h" #include "composite.h" #include "cursor.h" #include "input.h" #include "pointer_input.h" #include "scene_opengl.h" #include "wayland_server.h" namespace KWin { -AbstractBackend::AbstractBackend(QObject *parent) +Platform::Platform(QObject *parent) : QObject(parent) { } -AbstractBackend::~AbstractBackend() +Platform::~Platform() { } -QImage AbstractBackend::softwareCursor() const +QImage Platform::softwareCursor() const { return input()->pointer()->cursorImage(); } -QPoint AbstractBackend::softwareCursorHotspot() const +QPoint Platform::softwareCursorHotspot() const { return input()->pointer()->cursorHotSpot(); } -Screens *AbstractBackend::createScreens(QObject *parent) +Screens *Platform::createScreens(QObject *parent) { Q_UNUSED(parent) return nullptr; } -OpenGLBackend *AbstractBackend::createOpenGLBackend() +OpenGLBackend *Platform::createOpenGLBackend() { return nullptr; } -QPainterBackend *AbstractBackend::createQPainterBackend() +QPainterBackend *Platform::createQPainterBackend() { return nullptr; } -void AbstractBackend::configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config) +void Platform::configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config) { Q_UNUSED(config) qCWarning(KWIN_CORE) << "This backend does not support configuration changes."; } -void AbstractBackend::setSoftWareCursor(bool set) +void Platform::setSoftWareCursor(bool set) { if (m_softWareCursor == set) { return; } m_softWareCursor = set; if (m_softWareCursor) { - connect(Cursor::self(), &Cursor::posChanged, this, &AbstractBackend::triggerCursorRepaint); + connect(Cursor::self(), &Cursor::posChanged, this, &Platform::triggerCursorRepaint); } else { - disconnect(Cursor::self(), &Cursor::posChanged, this, &AbstractBackend::triggerCursorRepaint); + disconnect(Cursor::self(), &Cursor::posChanged, this, &Platform::triggerCursorRepaint); } } -void AbstractBackend::triggerCursorRepaint() +void Platform::triggerCursorRepaint() { if (!Compositor::self()) { return; } const QPoint &hotSpot = softwareCursorHotspot(); const QSize &size = softwareCursor().size(); Compositor::self()->addRepaint(m_cursor.lastRenderedPosition.x() - hotSpot.x(), m_cursor.lastRenderedPosition.y() - hotSpot.y(), size.width(), size.height()); } -void AbstractBackend::markCursorAsRendered() +void Platform::markCursorAsRendered() { if (m_softWareCursor) { m_cursor.lastRenderedPosition = Cursor::pos(); } if (input()->pointer()) { input()->pointer()->markCursorAsRendered(); } } -void AbstractBackend::keyboardKeyPressed(quint32 key, quint32 time) +void Platform::keyboardKeyPressed(quint32 key, quint32 time) { if (!input()) { return; } input()->processKeyboardKey(key, InputRedirection::KeyboardKeyPressed, time); } -void AbstractBackend::keyboardKeyReleased(quint32 key, quint32 time) +void Platform::keyboardKeyReleased(quint32 key, quint32 time) { if (!input()) { return; } input()->processKeyboardKey(key, InputRedirection::KeyboardKeyReleased, time); } -void AbstractBackend::keyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) +void Platform::keyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) { if (!input()) { return; } input()->processKeyboardModifiers(modsDepressed, modsLatched, modsLocked, group); } -void AbstractBackend::keymapChange(int fd, uint32_t size) +void Platform::keymapChange(int fd, uint32_t size) { if (!input()) { return; } input()->processKeymapChange(fd, size); } -void AbstractBackend::pointerAxisHorizontal(qreal delta, quint32 time) +void Platform::pointerAxisHorizontal(qreal delta, quint32 time) { if (!input()) { return; } input()->processPointerAxis(InputRedirection::PointerAxisHorizontal, delta, time); } -void AbstractBackend::pointerAxisVertical(qreal delta, quint32 time) +void Platform::pointerAxisVertical(qreal delta, quint32 time) { if (!input()) { return; } input()->processPointerAxis(InputRedirection::PointerAxisVertical, delta, time); } -void AbstractBackend::pointerButtonPressed(quint32 button, quint32 time) +void Platform::pointerButtonPressed(quint32 button, quint32 time) { if (!input()) { return; } input()->processPointerButton(button, InputRedirection::PointerButtonPressed, time); } -void AbstractBackend::pointerButtonReleased(quint32 button, quint32 time) +void Platform::pointerButtonReleased(quint32 button, quint32 time) { if (!input()) { return; } input()->processPointerButton(button, InputRedirection::PointerButtonReleased, time); } -void AbstractBackend::pointerMotion(const QPointF &position, quint32 time) +void Platform::pointerMotion(const QPointF &position, quint32 time) { if (!input()) { return; } input()->processPointerMotion(position, time); } -void AbstractBackend::touchCancel() +void Platform::touchCancel() { if (!input()) { return; } input()->cancelTouch(); } -void AbstractBackend::touchDown(qint32 id, const QPointF &pos, quint32 time) +void Platform::touchDown(qint32 id, const QPointF &pos, quint32 time) { if (!input()) { return; } input()->processTouchDown(id, pos, time); } -void AbstractBackend::touchFrame() +void Platform::touchFrame() { if (!input()) { return; } input()->touchFrame(); } -void AbstractBackend::touchMotion(qint32 id, const QPointF &pos, quint32 time) +void Platform::touchMotion(qint32 id, const QPointF &pos, quint32 time) { if (!input()) { return; } input()->processTouchMotion(id, pos, time); } -void AbstractBackend::touchUp(qint32 id, quint32 time) +void Platform::touchUp(qint32 id, quint32 time) { if (!input()) { return; } input()->processTouchUp(id, time); } -void AbstractBackend::repaint(const QRect &rect) +void Platform::repaint(const QRect &rect) { if (!Compositor::self()) { return; } Compositor::self()->addRepaint(rect); } -void AbstractBackend::setReady(bool ready) +void Platform::setReady(bool ready) { if (m_ready == ready) { return; } m_ready = ready; emit readyChanged(m_ready); } -void AbstractBackend::warpPointer(const QPointF &globalPos) +void Platform::warpPointer(const QPointF &globalPos) { Q_UNUSED(globalPos) } -bool AbstractBackend::supportsQpaContext() const +bool Platform::supportsQpaContext() const { return hasGLExtension(QByteArrayLiteral("EGL_KHR_surfaceless_context")); } -EGLDisplay AbstractBackend::sceneEglDisplay() const +EGLDisplay Platform::sceneEglDisplay() const { if (Compositor *c = Compositor::self()) { if (SceneOpenGL *s = dynamic_cast(c->scene())) { return static_cast(s->backend())->eglDisplay(); } } return EGL_NO_DISPLAY; } -EGLContext AbstractBackend::sceneEglContext() const +EGLContext Platform::sceneEglContext() const { if (Compositor *c = Compositor::self()) { if (SceneOpenGL *s = dynamic_cast(c->scene())) { return static_cast(s->backend())->context(); } } return EGL_NO_CONTEXT; } -QSize AbstractBackend::screenSize() const +QSize Platform::screenSize() const { return QSize(); } -QVector AbstractBackend::screenGeometries() const +QVector Platform::screenGeometries() const { return QVector({QRect(QPoint(0, 0), screenSize())}); } } diff --git a/abstract_backend.h b/abstract_backend.h index d597ad9e4..7a9bfaa59 100644 --- a/abstract_backend.h +++ b/abstract_backend.h @@ -1,188 +1,188 @@ /******************************************************************** 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_ABSTRACT_BACKEND_H #define KWIN_ABSTRACT_BACKEND_H #include #include #include #include #include namespace KWayland { namespace Server { class OutputConfigurationInterface; } } namespace KWin { class OpenGLBackend; class QPainterBackend; class Screens; class WaylandCursorTheme; -class KWIN_EXPORT AbstractBackend : public QObject +class KWIN_EXPORT Platform : public QObject { Q_OBJECT public: - virtual ~AbstractBackend(); + virtual ~Platform(); virtual void init() = 0; virtual Screens *createScreens(QObject *parent = nullptr); virtual OpenGLBackend *createOpenGLBackend(); virtual QPainterBackend *createQPainterBackend(); virtual void warpPointer(const QPointF &globalPos); /** * Whether our Compositing EGL display allows a surface less context * so that a sharing context could be created. **/ virtual bool supportsQpaContext() const; /** * The EGLDisplay used by the compositing scene. **/ virtual EGLDisplay sceneEglDisplay() const; /** * The EGLContext used by the compositing scene. **/ virtual EGLContext sceneEglContext() const; /** * Implementing subclasses should provide a size in case the backend represents * a basic screen and uses the BasicScreens. * * Base implementation returns an invalid size. **/ virtual QSize screenSize() const; /** * Implementing subclasses should provide all geometries in case the backend represents * a basic screen and uses the BasicScreens. * * Base implementation returns one QRect positioned at 0/0 with screenSize() as size. **/ virtual QVector screenGeometries() const; /** * Implement this method to receive configuration change requests through KWayland's * OutputManagement interface. * * Base implementation warns that the current backend does not implement this * functionality. */ virtual void configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config); bool usesSoftwareCursor() const { return m_softWareCursor; } QImage softwareCursor() const; QPoint softwareCursorHotspot() const; void markCursorAsRendered(); bool handlesOutputs() const { return m_handlesOutputs; } bool isReady() const { return m_ready; } void setInitialWindowSize(const QSize &size) { m_initialWindowSize = size; } void setDeviceIdentifier(const QByteArray &identifier) { m_deviceIdentifier = identifier; } bool supportsPointerWarping() const { return m_pointerWarping; } bool areOutputsEnabled() const { return m_outputsEnabled; } void setOutputsEnabled(bool enabled) { m_outputsEnabled = enabled; } int initialOutputCount() const { return m_initialOutputCount; } void setInitialOutputCount(int count) { m_initialOutputCount = count; } public Q_SLOTS: void pointerMotion(const QPointF &position, quint32 time); void pointerButtonPressed(quint32 button, quint32 time); void pointerButtonReleased(quint32 button, quint32 time); void pointerAxisHorizontal(qreal delta, quint32 time); void pointerAxisVertical(qreal delta, quint32 time); void keyboardKeyPressed(quint32 key, quint32 time); void keyboardKeyReleased(quint32 key, quint32 time); void keyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group); void keymapChange(int fd, uint32_t size); void touchDown(qint32 id, const QPointF &pos, quint32 time); void touchUp(qint32 id, quint32 time); void touchMotion(qint32 id, const QPointF &pos, quint32 time); void touchCancel(); void touchFrame(); Q_SIGNALS: void screensQueried(); void initFailed(); void cursorChanged(); void readyChanged(bool); /** * Emitted by backends using a one screen (nested window) approach and when the size of that changes. **/ void screenSizeChanged(); protected: - explicit AbstractBackend(QObject *parent = nullptr); + explicit Platform(QObject *parent = nullptr); void setSoftWareCursor(bool set); void handleOutputs() { m_handlesOutputs = true; } void repaint(const QRect &rect); void setReady(bool ready); QSize initialWindowSize() const { return m_initialWindowSize; } QByteArray deviceIdentifier() const { return m_deviceIdentifier; } void setSupportsPointerWarping(bool set) { m_pointerWarping = set; } private: void triggerCursorRepaint(); bool m_softWareCursor = false; struct { QPoint lastRenderedPosition; } m_cursor; bool m_handlesOutputs = false; bool m_ready = false; QSize m_initialWindowSize; QByteArray m_deviceIdentifier; bool m_pointerWarping = false; bool m_outputsEnabled = true; int m_initialOutputCount = 1; }; } -Q_DECLARE_INTERFACE(KWin::AbstractBackend, "org.kde.kwin.AbstractBackend") +Q_DECLARE_INTERFACE(KWin::Platform, "org.kde.kwin.Platform") #endif diff --git a/autotests/wayland/kwin_wayland_test.cpp b/autotests/wayland/kwin_wayland_test.cpp index 0d12834a5..d5f5386bb 100644 --- a/autotests/wayland/kwin_wayland_test.cpp +++ b/autotests/wayland/kwin_wayland_test.cpp @@ -1,279 +1,279 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "../../abstract_backend.h" #include "../../effects.h" #include "../../wayland_server.h" #include "../../workspace.h" #include "../../xcbutils.h" #include #include #include #include #include #include // system #include #include #include namespace KWin { static void readDisplay(int pipe); WaylandTestApplication::WaylandTestApplication(int &argc, char **argv) : Application(OperationModeXwayland, argc, argv) { #ifdef KWIN_BUILD_ACTIVITIES setUseKActivities(false); #endif qputenv("KWIN_COMPOSE", QByteArrayLiteral("Q")); initPlatform(KPluginMetaData(QStringLiteral(KWINBACKENDPATH))); WaylandServer::create(this); } WaylandTestApplication::~WaylandTestApplication() { kwinApp()->platform()->setOutputsEnabled(false); destroyWorkspace(); waylandServer()->dispatch(); // need to unload all effects prior to destroying X connection as they might do X calls if (effects) { static_cast(effects)->unloadAllEffects(); } 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 WaylandTestApplication::performStartup() { // first load options - done internally by a different thread createOptions(); waylandServer()->createInternalConnection(); // try creating the Wayland Backend createInput(); createBackend(); } void WaylandTestApplication::createBackend() { - AbstractBackend *backend = kwinApp()->platform(); - connect(backend, &AbstractBackend::screensQueried, this, &WaylandTestApplication::continueStartupWithScreens); - connect(backend, &AbstractBackend::initFailed, this, + Platform *platform = kwinApp()->platform(); + connect(platform, &Platform::screensQueried, this, &WaylandTestApplication::continueStartupWithScreens); + connect(platform, &Platform::initFailed, this, [] () { std::cerr << "FATAL ERROR: backend failed to initialize, exiting now" << std::endl; ::exit(1); } ); - backend->init(); + platform->init(); } void WaylandTestApplication::continueStartupWithScreens() { - disconnect(kwinApp()->platform(), &AbstractBackend::screensQueried, this, &WaylandTestApplication::continueStartupWithScreens); + disconnect(kwinApp()->platform(), &Platform::screensQueried, this, &WaylandTestApplication::continueStartupWithScreens); createScreens(); waylandServer()->initOutputs(); createCompositor(); startXwaylandServer(); } void WaylandTestApplication::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()) { ::exit(1); } createWorkspace(); Xcb::sync(); // Trigger possible errors, there's still a chance to abort } void WaylandTestApplication::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 WaylandTestApplication::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 QProcess(kwinApp()); m_xwaylandProcess->setProgram(QStringLiteral("Xwayland")); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); 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, &WaylandTestApplication::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); } } diff --git a/backends/drm/drm_backend.cpp b/backends/drm/drm_backend.cpp index 01addb3a7..15310c866 100644 --- a/backends/drm/drm_backend.cpp +++ b/backends/drm/drm_backend.cpp @@ -1,602 +1,602 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "drm_backend.h" #include "drm_output.h" #include "composite.h" #include "cursor.h" #include "logging.h" #include "logind.h" #include "scene_qpainter_drm_backend.h" #include "screens_drm.h" #include "udev.h" #include "virtual_terminal.h" #include "wayland_server.h" #if HAVE_GBM #include "egl_gbm_backend.h" #endif // KWayland #include // KF5 #include #include #include // Qt #include #include #include // system #include // drm #include #include #include #ifndef DRM_CAP_CURSOR_WIDTH #define DRM_CAP_CURSOR_WIDTH 0x8 #endif #ifndef DRM_CAP_CURSOR_HEIGHT #define DRM_CAP_CURSOR_HEIGHT 0x9 #endif namespace KWin { DrmBackend::DrmBackend(QObject *parent) - : AbstractBackend(parent) + : Platform(parent) , m_udev(new Udev) , m_udevMonitor(m_udev->monitor()) , m_dpmsFilter() { handleOutputs(); m_cursor[0] = nullptr; m_cursor[1] = nullptr; } DrmBackend::~DrmBackend() { if (m_fd >= 0) { // wait for pageflips while (m_pageFlipsPending != 0) { QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); } qDeleteAll(m_outputs); delete m_cursor[0]; delete m_cursor[1]; close(m_fd); } } void DrmBackend::init() { LogindIntegration *logind = LogindIntegration::self(); auto takeControl = [logind, this]() { if (logind->hasSessionControl()) { openDrm(); } else { logind->takeControl(); connect(logind, &LogindIntegration::hasSessionControlChanged, this, &DrmBackend::openDrm); } }; if (logind->isConnected()) { takeControl(); } else { connect(logind, &LogindIntegration::connectedChanged, this, takeControl); } auto v = VirtualTerminal::create(this); connect(v, &VirtualTerminal::activeChanged, this, &DrmBackend::activate); } void DrmBackend::outputWentOff() { if (!m_dpmsFilter.isNull()) { // already another output is off return; } m_dpmsFilter.reset(new DpmsInputEventFilter(this)); input()->prepandInputEventFilter(m_dpmsFilter.data()); } void DrmBackend::turnOutputsOn() { m_dpmsFilter.reset(); for (auto it = m_outputs.constBegin(), end = m_outputs.constEnd(); it != end; it++) { (*it)->setDpms(DrmOutput::DpmsMode::On); } } void DrmBackend::checkOutputsAreOn() { if (m_dpmsFilter.isNull()) { // already disabled, all outputs are on return; } for (auto it = m_outputs.constBegin(), end = m_outputs.constEnd(); it != end; it++) { if (!(*it)->isDpmsEnabled()) { // dpms still disabled, need to keep the filter return; } } // all outputs are on, disable the filter m_dpmsFilter.reset(); } void DrmBackend::activate(bool active) { if (active) { reactivate(); } else { deactivate(); } } void DrmBackend::reactivate() { if (m_active) { return; } m_active = true; DrmBuffer *c = m_cursor[(m_cursorIndex + 1) % 2]; const QPoint cp = Cursor::pos() - softwareCursorHotspot(); for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { DrmOutput *o = *it; o->pageFlipped(); o->blank(); o->showCursor(c); o->moveCursor(cp); } // restart compositor m_pageFlipsPending = 0; if (Compositor *compositor = Compositor::self()) { compositor->bufferSwapComplete(); compositor->addRepaintFull(); } } void DrmBackend::deactivate() { if (!m_active) { return; } // block compositor if (m_pageFlipsPending == 0 && Compositor::self()) { Compositor::self()->aboutToSwapBuffers(); } // hide cursor and disable for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { DrmOutput *o = *it; o->hideCursor(); o->restoreSaved(); } m_active = false; } void DrmBackend::pageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) { Q_UNUSED(fd) Q_UNUSED(frame) Q_UNUSED(sec) Q_UNUSED(usec) auto output = reinterpret_cast(data); output->pageFlipped(); output->m_backend->m_pageFlipsPending--; if (output->m_backend->m_pageFlipsPending == 0) { // TODO: improve, this currently means we wait for all page flips or all outputs. // It would be better to driver the repaint per output if (Compositor::self()) { Compositor::self()->bufferSwapComplete(); } } } void DrmBackend::openDrm() { connect(LogindIntegration::self(), &LogindIntegration::sessionActiveChanged, this, &DrmBackend::activate); VirtualTerminal::self()->init(); UdevDevice::Ptr device = m_udev->primaryGpu(); if (!device) { qCWarning(KWIN_DRM) << "Did not find a GPU"; return; } int fd = LogindIntegration::self()->takeDevice(device->devNode()); if (fd < 0) { qCWarning(KWIN_DRM) << "failed to open drm device at" << device->devNode(); return; } m_fd = fd; m_active = true; QSocketNotifier *notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this); connect(notifier, &QSocketNotifier::activated, this, [this] { if (!VirtualTerminal::self()->isActive()) { return; } drmEventContext e; memset(&e, 0, sizeof e); e.version = DRM_EVENT_CONTEXT_VERSION; e.page_flip_handler = pageFlipHandler; drmHandleEvent(m_fd, &e); } ); m_drmId = device->sysNum(); queryResources(); // setup udevMonitor if (m_udevMonitor) { m_udevMonitor->filterSubsystemDevType("drm"); const int fd = m_udevMonitor->fd(); if (fd != -1) { QSocketNotifier *notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); connect(notifier, &QSocketNotifier::activated, this, [this] { auto device = m_udevMonitor->getDevice(); if (!device) { return; } if (device->sysNum() != m_drmId) { return; } if (device->hasProperty("HOTPLUG", "1")) { qCDebug(KWIN_DRM) << "Received hot plug event for monitored drm device"; queryResources(); m_cursorIndex = (m_cursorIndex + 1) % 2; updateCursor(); } } ); m_udevMonitor->enable(); } } setReady(true); initCursor(); } void DrmBackend::queryResources() { if (m_fd < 0) { return; } ScopedDrmPointer<_drmModeRes, &drmModeFreeResources> resources(drmModeGetResources(m_fd)); if (!resources) { qCWarning(KWIN_DRM) << "drmModeGetResources failed"; return; } QVector connectedOutputs; for (int i = 0; i < resources->count_connectors; ++i) { const auto id = resources->connectors[i]; ScopedDrmPointer<_drmModeConnector, &drmModeFreeConnector> connector(drmModeGetConnector(m_fd, id)); if (!connector) { continue; } if (connector->connection != DRM_MODE_CONNECTED) { continue; } if (connector->count_modes == 0) { continue; } if (DrmOutput *o = findOutput(connector->connector_id)) { connectedOutputs << o; continue; } bool crtcFound = false; const quint32 crtcId = findCrtc(resources.data(), connector.data(), &crtcFound); if (!crtcFound) { continue; } ScopedDrmPointer<_drmModeCrtc, &drmModeFreeCrtc> crtc(drmModeGetCrtc(m_fd, crtcId)); if (!crtc) { continue; } DrmOutput *drmOutput = new DrmOutput(this); connect(drmOutput, &DrmOutput::dpmsChanged, this, &DrmBackend::outputDpmsChanged); drmOutput->m_crtcId = crtcId; if (crtc->mode_valid) { drmOutput->m_mode = crtc->mode; } else { drmOutput->m_mode = connector->modes[0]; } drmOutput->m_connector = connector->connector_id; drmOutput->init(connector.data()); qCDebug(KWIN_DRM) << "Found new output with uuid" << drmOutput->uuid(); connectedOutputs << drmOutput; } std::sort(connectedOutputs.begin(), connectedOutputs.end(), [] (DrmOutput *a, DrmOutput *b) { return a->m_connector < b->m_connector; }); // check for outputs which got removed auto it = m_outputs.begin(); while (it != m_outputs.end()) { if (connectedOutputs.contains(*it)) { it++; continue; } DrmOutput *removed = *it; it = m_outputs.erase(it); emit outputRemoved(removed); delete removed; } for (auto it = connectedOutputs.constBegin(); it != connectedOutputs.constEnd(); ++it) { if (!m_outputs.contains(*it)) { emit outputAdded(*it); } } m_outputs = connectedOutputs; readOutputsConfiguration(); emit screensQueried(); } void DrmBackend::readOutputsConfiguration() { if (m_outputs.isEmpty()) { return; } const QByteArray uuid = generateOutputConfigurationUuid(); const auto outputGroup = kwinApp()->config()->group("DrmOutputs"); const auto configGroup = outputGroup.group(uuid); qCDebug(KWIN_DRM) << "Reading output configuration for" << uuid; // default position goes from left to right QPoint pos(0, 0); for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) { const auto outputConfig = configGroup.group((*it)->uuid()); (*it)->setGlobalPos(outputConfig.readEntry("Position", pos)); // TODO: add mode pos.setX(pos.x() + (*it)->size().width()); } } QByteArray DrmBackend::generateOutputConfigurationUuid() const { auto it = m_outputs.constBegin(); if (m_outputs.size() == 1) { // special case: one output return (*it)->uuid(); } QCryptographicHash hash(QCryptographicHash::Md5); for (; it != m_outputs.constEnd(); ++it) { hash.addData((*it)->uuid()); } return hash.result().toHex().left(10); } void DrmBackend::configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config) { const auto changes = config->changes(); for (auto it = changes.begin(); it != changes.end(); it++) { KWayland::Server::OutputChangeSet *changeset = it.value(); auto drmoutput = findOutput(it.key()->uuid()); if (drmoutput == nullptr) { qCWarning(KWIN_DRM) << "Could NOT find DrmOutput matching " << it.key()->uuid(); return; } drmoutput->setChanges(changeset); } } DrmOutput *DrmBackend::findOutput(quint32 connector) { auto it = std::find_if(m_outputs.constBegin(), m_outputs.constEnd(), [connector] (DrmOutput *o) { return o->m_connector == connector; }); if (it != m_outputs.constEnd()) { return *it; } return nullptr; } DrmOutput *DrmBackend::findOutput(const QByteArray &uuid) { auto it = std::find_if(m_outputs.constBegin(), m_outputs.constEnd(), [uuid] (DrmOutput *o) { return o->m_uuid == uuid; }); if (it != m_outputs.constEnd()) { return *it; } return nullptr; } quint32 DrmBackend::findCrtc(drmModeRes *res, drmModeConnector *connector, bool *ok) { if (ok) { *ok = false; } ScopedDrmPointer<_drmModeEncoder, &drmModeFreeEncoder> encoder(drmModeGetEncoder(m_fd, connector->encoder_id)); if (encoder) { if (!crtcIsUsed(encoder->crtc_id)) { if (ok) { *ok = true; } return encoder->crtc_id; } } // let's iterate over all encoders to find a suitable crtc for (int i = 0; i < connector->count_encoders; ++i) { ScopedDrmPointer<_drmModeEncoder, &drmModeFreeEncoder> encoder(drmModeGetEncoder(m_fd, connector->encoders[i])); if (!encoder) { continue; } for (int j = 0; j < res->count_crtcs; ++j) { if (!(encoder->possible_crtcs & (1 << j))) { continue; } if (!crtcIsUsed(res->crtcs[j])) { if (ok) { *ok = true; } return res->crtcs[j]; } } } return 0; } bool DrmBackend::crtcIsUsed(quint32 crtc) { auto it = std::find_if(m_outputs.constBegin(), m_outputs.constEnd(), [crtc] (DrmOutput *o) { return o->m_crtcId == crtc; } ); return it != m_outputs.constEnd(); } void DrmBackend::present(DrmBuffer *buffer, DrmOutput *output) { if (output->present(buffer)) { m_pageFlipsPending++; if (m_pageFlipsPending == 1 && Compositor::self()) { Compositor::self()->aboutToSwapBuffers(); } } } void DrmBackend::initCursor() { uint64_t capability = 0; QSize cursorSize; if (drmGetCap(m_fd, DRM_CAP_CURSOR_WIDTH, &capability) == 0) { cursorSize.setWidth(capability); } else { cursorSize.setWidth(64); } if (drmGetCap(m_fd, DRM_CAP_CURSOR_HEIGHT, &capability) == 0) { cursorSize.setHeight(capability); } else { cursorSize.setHeight(64); } m_cursor[0] = createBuffer(cursorSize); m_cursor[0]->map(QImage::Format_ARGB32_Premultiplied); m_cursor[0]->image()->fill(Qt::transparent); m_cursor[1] = createBuffer(cursorSize); m_cursor[1]->map(QImage::Format_ARGB32_Premultiplied); m_cursor[0]->image()->fill(Qt::transparent); // now we have screens and can set cursors, so start tracking connect(this, &DrmBackend::cursorChanged, this, &DrmBackend::updateCursor); connect(Cursor::self(), &Cursor::posChanged, this, &DrmBackend::moveCursor); } void DrmBackend::setCursor() { DrmBuffer *c = m_cursor[m_cursorIndex]; m_cursorIndex = (m_cursorIndex + 1) % 2; for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { (*it)->showCursor(c); } markCursorAsRendered(); } void DrmBackend::updateCursor() { const QImage &cursorImage = softwareCursor(); if (cursorImage.isNull()) { hideCursor(); return; } QImage *c = m_cursor[m_cursorIndex]->image(); c->fill(Qt::transparent); QPainter p; p.begin(c); p.drawImage(QPoint(0, 0), cursorImage); p.end(); setCursor(); moveCursor(); } void DrmBackend::hideCursor() { for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { (*it)->hideCursor(); } } void DrmBackend::moveCursor() { const QPoint p = Cursor::pos() - softwareCursorHotspot(); for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { (*it)->moveCursor(p); } } QSize DrmBackend::size() const { if (m_outputs.isEmpty()) { return QSize(); } return m_outputs.first()->size(); } Screens *DrmBackend::createScreens(QObject *parent) { return new DrmScreens(this, parent); } QPainterBackend *DrmBackend::createQPainterBackend() { return new DrmQPainterBackend(this); } OpenGLBackend *DrmBackend::createOpenGLBackend() { #if HAVE_GBM return new EglGbmBackend(this); #else - return AbstractBackend::createOpenGLBackend(); + return Platform::createOpenGLBackend(); #endif } DrmBuffer *DrmBackend::createBuffer(const QSize &size) { DrmBuffer *b = new DrmBuffer(this, size); m_buffers << b; return b; } DrmBuffer *DrmBackend::createBuffer(gbm_surface *surface) { #if HAVE_GBM DrmBuffer *b = new DrmBuffer(this, surface); m_buffers << b; return b; #else return nullptr; #endif } void DrmBackend::bufferDestroyed(DrmBuffer *b) { m_buffers.removeAll(b); } void DrmBackend::outputDpmsChanged() { if (m_outputs.isEmpty()) { return; } bool enabled = false; for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { enabled = enabled || (*it)->isDpmsEnabled(); } setOutputsEnabled(enabled); } } diff --git a/backends/drm/drm_backend.h b/backends/drm/drm_backend.h index 3aec6137e..1fa9d1a52 100644 --- a/backends/drm/drm_backend.h +++ b/backends/drm/drm_backend.h @@ -1,137 +1,137 @@ /******************************************************************** 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_DRM_BACKEND_H #define KWIN_DRM_BACKEND_H #include "abstract_backend.h" #include "input.h" #include "drm_buffer.h" #include "drm_inputeventfilter.h" #include "drm_pointer.h" #include #include #include #include #include struct gbm_bo; struct gbm_surface; namespace KWayland { namespace Server { class OutputInterface; class OutputDeviceInterface; class OutputChangeSet; class OutputManagementInterface; } } namespace KWin { class Udev; class UdevMonitor; class DrmOutput; -class KWIN_EXPORT DrmBackend : public AbstractBackend +class KWIN_EXPORT DrmBackend : public Platform { Q_OBJECT - Q_INTERFACES(KWin::AbstractBackend) - Q_PLUGIN_METADATA(IID "org.kde.kwin.AbstractBackend" FILE "drm.json") + Q_INTERFACES(KWin::Platform) + Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "drm.json") public: explicit DrmBackend(QObject *parent = nullptr); virtual ~DrmBackend(); void configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config) override; Screens *createScreens(QObject *parent = nullptr) override; QPainterBackend *createQPainterBackend() override; OpenGLBackend* createOpenGLBackend() override; void init() override; DrmBuffer *createBuffer(const QSize &size); DrmBuffer *createBuffer(gbm_surface *surface); void present(DrmBuffer *buffer, DrmOutput *output); QSize size() const; int fd() const { return m_fd; } QVector outputs() const { return m_outputs; } QVector buffers() const { return m_buffers; } void bufferDestroyed(DrmBuffer *b); void outputWentOff(); void checkOutputsAreOn(); public Q_SLOTS: void turnOutputsOn(); Q_SIGNALS: void outputRemoved(KWin::DrmOutput *output); void outputAdded(KWin::DrmOutput *output); private: static void pageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data); void openDrm(); void activate(bool active); void reactivate(); void deactivate(); void queryResources(); void setCursor(); void updateCursor(); void hideCursor(); void moveCursor(); void initCursor(); quint32 findCrtc(drmModeRes *res, drmModeConnector *connector, bool *ok = nullptr); bool crtcIsUsed(quint32 crtc); void outputDpmsChanged(); void readOutputsConfiguration(); QByteArray generateOutputConfigurationUuid() const; DrmOutput *findOutput(quint32 connector); DrmOutput *findOutput(const QByteArray &uuid); QScopedPointer m_udev; QScopedPointer m_udevMonitor; int m_fd = -1; int m_drmId = 0; QVector m_outputs; DrmBuffer *m_cursor[2]; int m_cursorIndex = 0; int m_pageFlipsPending = 0; bool m_active = false; QVector m_buffers; QScopedPointer m_dpmsFilter; KWayland::Server::OutputManagementInterface *m_outputManagement = nullptr; }; } #endif diff --git a/backends/fbdev/fb_backend.cpp b/backends/fbdev/fb_backend.cpp index f5efe8b85..95b0925e6 100644 --- a/backends/fbdev/fb_backend.cpp +++ b/backends/fbdev/fb_backend.cpp @@ -1,205 +1,205 @@ /******************************************************************** 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 "fb_backend.h" #include "composite.h" #include "logging.h" #include "logind.h" #include "scene_qpainter_fb_backend.h" #include "screens.h" #include "virtual_terminal.h" // system #include #include #include #include // Linux #include namespace KWin { FramebufferBackend::FramebufferBackend(QObject *parent) - : AbstractBackend(parent) + : Platform(parent) { setSoftWareCursor(true); } FramebufferBackend::~FramebufferBackend() { unmap(); if (m_fd >= 0) { close(m_fd); } } Screens *FramebufferBackend::createScreens(QObject *parent) { return new BasicScreens(this, parent); } QPainterBackend *FramebufferBackend::createQPainterBackend() { return new FramebufferQPainterBackend(this); } void FramebufferBackend::init() { LogindIntegration *logind = LogindIntegration::self(); auto takeControl = [logind, this]() { if (logind->hasSessionControl()) { openFrameBuffer(); } else { logind->takeControl(); connect(logind, &LogindIntegration::hasSessionControlChanged, this, &FramebufferBackend::openFrameBuffer); } }; if (logind->isConnected()) { takeControl(); } else { connect(logind, &LogindIntegration::connectedChanged, this, takeControl); } VirtualTerminal::create(this); } void FramebufferBackend::openFrameBuffer() { VirtualTerminal::self()->init(); int fd = LogindIntegration::self()->takeDevice(deviceIdentifier().constData()); if (fd < 0) { qCWarning(KWIN_FB) << "Failed to open frame buffer device through logind, trying without"; } fd = open(deviceIdentifier().constData(), O_RDWR | O_CLOEXEC); if (fd < 0) { qCWarning(KWIN_FB) << "failed to open frame buffer device"; return; } m_fd = fd; queryScreenInfo(); setReady(true); emit screensQueried(); } bool FramebufferBackend::queryScreenInfo() { if (m_fd < 0) { return false; } fb_var_screeninfo varinfo; fb_fix_screeninfo fixinfo; // Probe the device for screen information. if (ioctl(m_fd, FBIOGET_FSCREENINFO, &fixinfo) < 0 || ioctl(m_fd, FBIOGET_VSCREENINFO, &varinfo) < 0) { return false; } m_resolution = QSize(varinfo.xres, varinfo.yres); m_physicalSize = QSize(varinfo.width, varinfo.height); m_id = QByteArray(fixinfo.id); m_red = {varinfo.red.offset, varinfo.red.length}; m_green = {varinfo.green.offset, varinfo.green.length}; m_blue = {varinfo.blue.offset, varinfo.blue.length}; m_alpha = {varinfo.transp.offset, varinfo.transp.length}; m_bitsPerPixel = varinfo.bits_per_pixel; m_bufferLength = fixinfo.smem_len; m_bytesPerLine = fixinfo.line_length; return true; } void FramebufferBackend::map() { if (m_memory) { // already mapped; return; } if (m_fd < 0) { // not valid return; } void *mem = mmap(nullptr, m_bufferLength, PROT_WRITE, MAP_SHARED, m_fd, 0); if (mem == MAP_FAILED) { qCWarning(KWIN_FB) << "Failed to mmap frame buffer"; return; } m_memory = mem; } void FramebufferBackend::unmap() { if (!m_memory) { return; } if (munmap(m_memory, m_bufferLength) < 0) { qCWarning(KWIN_FB) << "Failed to munmap frame buffer"; } m_memory = nullptr; } QImage::Format FramebufferBackend::imageFormat() const { if (m_fd < 0) { return QImage::Format_Invalid; } qCDebug(KWIN_FB) << "Bits Per Pixel: " << m_bitsPerPixel; qCDebug(KWIN_FB) << "Buffer Length: " << m_bufferLength; qCDebug(KWIN_FB) << "Bytes Per Line: " << m_bytesPerLine; qCDebug(KWIN_FB) << "Alpha Length: " << m_alpha.length; qCDebug(KWIN_FB) << "Red Length: " << m_red.length; qCDebug(KWIN_FB) << "Green Length: " << m_green.length; qCDebug(KWIN_FB) << "Blue Length: " << m_blue.length; qCDebug(KWIN_FB) << "Blue Offset: " << m_blue.offset; qCDebug(KWIN_FB) << "Green Offset: " << m_green.offset; qCDebug(KWIN_FB) << "Red Offset: " << m_red.offset; qCDebug(KWIN_FB) << "Alpha Offset: " << m_alpha.offset; if (m_bitsPerPixel == 32 && m_red.length == 8 && m_green.length == 8 && m_blue.length == 8 && m_blue.offset == 0 && m_green.offset == 8 && m_red.offset == 16) { qCDebug(KWIN_FB) << "Framebuffer format is RGB32"; return QImage::Format_RGB32; } else if (m_bitsPerPixel == 24 && m_red.length == 8 && m_green.length == 8 && m_blue.length == 8 && m_blue.offset == 0 && m_green.offset == 8 && m_red.offset == 16) { qCDebug(KWIN_FB) << "Framebuffer Format is RGB888"; return QImage::Format_RGB888; } else if (m_bitsPerPixel == 16 && m_red.length == 5 && m_green.length == 6 && m_blue.length == 5 && m_blue.offset == 0 && m_green.offset == 5 && m_red.offset == 11) { qCDebug(KWIN_FB) << "Framebuffer Format is RGB16"; return QImage::Format_RGB16; } qCWarning(KWIN_FB) << "Framebuffer format is unknown"; return QImage::Format_Invalid; } } diff --git a/backends/fbdev/fb_backend.h b/backends/fbdev/fb_backend.h index ef0232f49..afdd88ff3 100644 --- a/backends/fbdev/fb_backend.h +++ b/backends/fbdev/fb_backend.h @@ -1,98 +1,98 @@ /******************************************************************** 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_FB_BACKEND_H #define KWIN_FB_BACKEND_H #include "abstract_backend.h" #include #include namespace KWin { -class KWIN_EXPORT FramebufferBackend : public AbstractBackend +class KWIN_EXPORT FramebufferBackend : public Platform { Q_OBJECT - Q_INTERFACES(KWin::AbstractBackend) - Q_PLUGIN_METADATA(IID "org.kde.kwin.AbstractBackend" FILE "fbdev.json") + Q_INTERFACES(KWin::Platform) + Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "fbdev.json") public: explicit FramebufferBackend(QObject *parent = nullptr); virtual ~FramebufferBackend(); Screens *createScreens(QObject *parent = nullptr) override; QPainterBackend *createQPainterBackend() override; QSize screenSize() const override { return m_resolution; } void init() override; bool isValid() const { return m_fd >= 0; } QSize size() const { return m_resolution; } QSize physicalSize() const { return m_physicalSize; } void map(); void unmap(); void *mappedMemory() const { return m_memory; } int bytesPerLine() const { return m_bytesPerLine; } int bufferSize() const { return m_bufferLength; } quint32 bitsPerPixel() const { return m_bitsPerPixel; } QImage::Format imageFormat() const; private: void openFrameBuffer(); bool queryScreenInfo(); QSize m_resolution; QSize m_physicalSize; QByteArray m_id; struct Color { quint32 offset; quint32 length; }; Color m_red; Color m_green; Color m_blue; Color m_alpha; quint32 m_bitsPerPixel = 0; int m_fd = -1; quint32 m_bufferLength = 0; int m_bytesPerLine = 0; void *m_memory = nullptr; }; } #endif diff --git a/backends/hwcomposer/hwcomposer_backend.cpp b/backends/hwcomposer/hwcomposer_backend.cpp index 720ac1d73..1e7484094 100644 --- a/backends/hwcomposer/hwcomposer_backend.cpp +++ b/backends/hwcomposer/hwcomposer_backend.cpp @@ -1,456 +1,456 @@ /******************************************************************** 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 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "egl_hwcomposer_backend.h" #include "hwcomposer_backend.h" #include "logging.h" #include "screens_hwcomposer.h" #include "composite.h" #include "input.h" #include "main.h" #include "virtual_terminal.h" #include "wayland_server.h" // KWayland #include #include #include // Qt #include // hybris/android #include #include #include // linux #include // based on test_hwcomposer.c from libhybris project (Apache 2 licensed) namespace KWin { BacklightInputEventFilter::BacklightInputEventFilter(HwcomposerBackend *backend) : InputEventFilter() , m_backend(backend) { } BacklightInputEventFilter::~BacklightInputEventFilter() = default; bool BacklightInputEventFilter::pointerEvent(QMouseEvent *event, quint32 nativeButton) { Q_UNUSED(event) Q_UNUSED(nativeButton) if (!m_backend->isBacklightOff()) { return false; } toggleBacklight(); return true; } bool BacklightInputEventFilter::wheelEvent(QWheelEvent *event) { Q_UNUSED(event) if (!m_backend->isBacklightOff()) { return false; } toggleBacklight(); return true; } bool BacklightInputEventFilter::keyEvent(QKeyEvent *event) { if (event->key() == Qt::Key_PowerOff && event->type() == QEvent::KeyRelease) { toggleBacklight(); return true; } return m_backend->isBacklightOff(); } bool BacklightInputEventFilter::touchDown(quint32 id, const QPointF &pos, quint32 time) { Q_UNUSED(pos) Q_UNUSED(time) if (!m_backend->isBacklightOff()) { return false; } if (m_touchPoints.isEmpty()) { if (!m_doubleTapTimer.isValid()) { // this is the first tap m_doubleTapTimer.start(); } else { if (m_doubleTapTimer.elapsed() < qApp->doubleClickInterval()) { m_secondTap = true; } else { // took too long. Let's consider it a new click m_doubleTapTimer.restart(); } } } else { // not a double tap m_doubleTapTimer.invalidate(); m_secondTap = false; } m_touchPoints << id; return true; } bool BacklightInputEventFilter::touchUp(quint32 id, quint32 time) { Q_UNUSED(time) m_touchPoints.removeAll(id); if (!m_backend->isBacklightOff()) { return false; } if (m_touchPoints.isEmpty() && m_doubleTapTimer.isValid() && m_secondTap) { if (m_doubleTapTimer.elapsed() < qApp->doubleClickInterval()) { toggleBacklight(); } m_doubleTapTimer.invalidate(); m_secondTap = false; } return true; } bool BacklightInputEventFilter::touchMotion(quint32 id, const QPointF &pos, quint32 time) { Q_UNUSED(id) Q_UNUSED(pos) Q_UNUSED(time) return m_backend->isBacklightOff(); } void BacklightInputEventFilter::toggleBacklight() { // queued to not modify the list of event filters while filtering QMetaObject::invokeMethod(m_backend, "toggleBlankOutput", Qt::QueuedConnection); } HwcomposerBackend::HwcomposerBackend(QObject *parent) - : AbstractBackend(parent) + : Platform(parent) { handleOutputs(); } HwcomposerBackend::~HwcomposerBackend() { if (!m_outputBlank) { toggleBlankOutput(); } if (m_device) { hwc_close_1(m_device); } } static KWayland::Server::OutputInterface *createOutput(hwc_composer_device_1_t *device) { uint32_t configs[5]; size_t numConfigs = 5; if (device->getDisplayConfigs(device, 0, configs, &numConfigs) != 0) { qCWarning(KWIN_HWCOMPOSER) << "Failed to get hwcomposer display configurations"; return nullptr; } int32_t attr_values[5]; uint32_t attributes[] = { HWC_DISPLAY_WIDTH, HWC_DISPLAY_HEIGHT, HWC_DISPLAY_DPI_X, HWC_DISPLAY_DPI_Y, HWC_DISPLAY_VSYNC_PERIOD , HWC_DISPLAY_NO_ATTRIBUTE }; device->getDisplayAttributes(device, 0, configs[0], attributes, attr_values); QSize pixel(attr_values[0], attr_values[1]); if (pixel.isEmpty()) { return nullptr; } using namespace KWayland::Server; OutputInterface *o = waylandServer()->display()->createOutput(waylandServer()->display()); o->addMode(pixel, OutputInterface::ModeFlag::Current | OutputInterface::ModeFlag::Preferred, (attr_values[4] == 0) ? 60000 : 10E11/attr_values[4]); if (attr_values[2] != 0 && attr_values[3] != 0) { static const qreal factor = 25.4; o->setPhysicalSize(QSizeF(qreal(pixel.width() * 1000) / qreal(attr_values[2]) * factor, qreal(pixel.height() * 1000) / qreal(attr_values[3]) * factor).toSize()); } else { // couldn't read physical size, assume 96 dpi o->setPhysicalSize(pixel / 3.8); } o->create(); return o; } void HwcomposerBackend::init() { hw_module_t *hwcModule = nullptr; if (hw_get_module(HWC_HARDWARE_MODULE_ID, (const hw_module_t **)&hwcModule) != 0) { qCWarning(KWIN_HWCOMPOSER) << "Failed to get hwcomposer module"; emit initFailed(); return; } hwc_composer_device_1_t *hwcDevice = nullptr; if (hwc_open_1(hwcModule, &hwcDevice) != 0) { qCWarning(KWIN_HWCOMPOSER) << "Failed to open hwcomposer device"; emit initFailed(); return; } // unblank, setPowerMode? m_device = hwcDevice; // register callbacks hwc_procs_t *procs = new hwc_procs_t; procs->invalidate = [] (const struct hwc_procs* procs) { Q_UNUSED(procs) }; procs->vsync = [] (const struct hwc_procs* procs, int disp, int64_t timestamp) { Q_UNUSED(procs) if (disp != 0) { return; } dynamic_cast(kwinApp()->platform())->wakeVSync(); }; procs->hotplug = [] (const struct hwc_procs* procs, int disp, int connected) { Q_UNUSED(procs) Q_UNUSED(disp) Q_UNUSED(connected) }; m_device->registerProcs(m_device, procs); initLights(); toggleBlankOutput(); m_filter.reset(new BacklightInputEventFilter(this)); input()->prepandInputEventFilter(m_filter.data()); // get display configuration auto output = createOutput(hwcDevice); if (!output) { emit initFailed(); return; } m_displaySize = output->pixelSize(); m_refreshRate = output->refreshRate(); if (m_refreshRate != 0) { m_vsyncInterval = 1000000/m_refreshRate; } if (m_lights) { using namespace KWayland::Server; output->setDpmsSupported(true); auto updateDpms = [this, output] { output->setDpmsMode(m_outputBlank ? OutputInterface::DpmsMode::Off : OutputInterface::DpmsMode::On); }; updateDpms(); connect(this, &HwcomposerBackend::outputBlankChanged, this, updateDpms); connect(output, &OutputInterface::dpmsModeRequested, this, [this] (KWayland::Server::OutputInterface::DpmsMode mode) { if (mode == OutputInterface::DpmsMode::On) { if (m_outputBlank) { toggleBlankOutput(); } } else { if (!m_outputBlank) { toggleBlankOutput(); } } } ); } qCDebug(KWIN_HWCOMPOSER) << "Display size:" << m_displaySize; qCDebug(KWIN_HWCOMPOSER) << "Refresh rate:" << m_refreshRate; VirtualTerminal::create(this); VirtualTerminal::self()->init(); emit screensQueried(); setReady(true); } void HwcomposerBackend::initLights() { hw_module_t *lightsModule = nullptr; if (hw_get_module(LIGHTS_HARDWARE_MODULE_ID, (const hw_module_t **)&lightsModule) != 0) { qCWarning(KWIN_HWCOMPOSER) << "Failed to get lights module"; return; } light_device_t *lightsDevice = nullptr; if (lightsModule->methods->open(lightsModule, LIGHT_ID_BACKLIGHT, (hw_device_t **)&lightsDevice) != 0) { qCWarning(KWIN_HWCOMPOSER) << "Failed to create lights device"; return; } m_lights = lightsDevice; } void HwcomposerBackend::toggleBlankOutput() { if (!m_device) { return; } m_outputBlank = !m_outputBlank; toggleScreenBrightness(); m_device->blank(m_device, 0, m_outputBlank ? 1 : 0); // only disable Vsycn, enable happens after next frame rendered if (m_outputBlank) { enableVSync(false); } // enable/disable compositor repainting when blanked setOutputsEnabled(!m_outputBlank); if (Compositor *compositor = Compositor::self()) { if (!m_outputBlank) { compositor->addRepaintFull(); } } emit outputBlankChanged(); } void HwcomposerBackend::toggleScreenBrightness() { if (!m_lights) { return; } const int brightness = m_outputBlank ? 0 : 0xFF; struct light_state_t state; state.flashMode = LIGHT_FLASH_NONE; state.brightnessMode = BRIGHTNESS_MODE_USER; state.color = (int)((0xffU << 24) | (brightness << 16) | (brightness << 8) | brightness); m_lights->set_light(m_lights, &state); } void HwcomposerBackend::enableVSync(bool enable) { if (m_hasVsync == enable) { return; } const int result = m_device->eventControl(m_device, 0, HWC_EVENT_VSYNC, enable ? 1: 0); m_hasVsync = enable && (result == 0); } HwcomposerWindow *HwcomposerBackend::createSurface() { return new HwcomposerWindow(this); } Screens *HwcomposerBackend::createScreens(QObject *parent) { return new HwcomposerScreens(this, parent); } OpenGLBackend *HwcomposerBackend::createOpenGLBackend() { return new EglHwcomposerBackend(this); } void HwcomposerBackend::waitVSync() { if (!m_hasVsync) { return; } m_vsyncMutex.lock(); m_vsyncWaitCondition.wait(&m_vsyncMutex, m_vsyncInterval); m_vsyncMutex.unlock(); } void HwcomposerBackend::wakeVSync() { m_vsyncMutex.lock(); m_vsyncWaitCondition.wakeAll(); m_vsyncMutex.unlock(); } static void initLayer(hwc_layer_1_t *layer, const hwc_rect_t &rect) { memset(layer, 0, sizeof(hwc_layer_1_t)); layer->compositionType = HWC_FRAMEBUFFER; layer->hints = 0; layer->flags = 0; layer->handle = 0; layer->transform = 0; layer->blending = HWC_BLENDING_NONE; layer->sourceCrop = rect; layer->displayFrame = rect; layer->visibleRegionScreen.numRects = 1; layer->visibleRegionScreen.rects = &layer->displayFrame; layer->acquireFenceFd = -1; layer->releaseFenceFd = -1; layer->planeAlpha = 0xFF; } HwcomposerWindow::HwcomposerWindow(HwcomposerBackend *backend) : HWComposerNativeWindow(backend->size().width(), backend->size().height(), HAL_PIXEL_FORMAT_RGB_888) , m_backend(backend) { setBufferCount(3); size_t size = sizeof(hwc_display_contents_1_t) + 2 * sizeof(hwc_layer_1_t); hwc_display_contents_1_t *list = (hwc_display_contents_1_t*)malloc(size); m_list = (hwc_display_contents_1_t**)malloc(HWC_NUM_DISPLAY_TYPES * sizeof(hwc_display_contents_1_t *)); for (int i = 0; i < HWC_NUM_DISPLAY_TYPES; ++i) { m_list[i] = nullptr; } // Assign buffer only to the first item, otherwise you get tearing // if passed the same to multiple places // see https://github.com/mer-hybris/qt5-qpa-hwcomposer-plugin/commit/f1d802151e8a4f5d10d60eb8de8e07552b93a34a m_list[0] = list; const hwc_rect_t rect = { 0, 0, m_backend->size().width(), m_backend->size().height() }; initLayer(&list->hwLayers[0], rect); initLayer(&list->hwLayers[1], rect); list->retireFenceFd = -1; list->flags = HWC_GEOMETRY_CHANGED; list->numHwLayers = 2; } HwcomposerWindow::~HwcomposerWindow() { // TODO: cleanup } void HwcomposerWindow::present(HWComposerNativeWindowBuffer *buffer) { m_backend->waitVSync(); hwc_composer_device_1_t *device = m_backend->device(); auto fblayer = &m_list[0]->hwLayers[1]; fblayer->handle = buffer->handle; fblayer->acquireFenceFd = getFenceBufferFd(buffer); fblayer->releaseFenceFd = -1; int err = device->prepare(device, 1, m_list); assert(err == 0); err = device->set(device, 1, m_list); assert(err == 0); m_backend->enableVSync(true); setFenceBufferFd(buffer, fblayer->releaseFenceFd); if (m_list[0]->retireFenceFd != -1) { close(m_list[0]->retireFenceFd); m_list[0]->retireFenceFd = -1; } m_list[0]->flags = 0; } } diff --git a/backends/hwcomposer/hwcomposer_backend.h b/backends/hwcomposer/hwcomposer_backend.h index 5c8fc24e5..c94f6cfc5 100644 --- a/backends/hwcomposer/hwcomposer_backend.h +++ b/backends/hwcomposer/hwcomposer_backend.h @@ -1,142 +1,142 @@ /******************************************************************** 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 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_HWCOMPOSER_BACKEND_H #define KWIN_HWCOMPOSER_BACKEND_H #include "abstract_backend.h" #include "input.h" #include #include #include // libhybris #include // needed as hwcomposer_window.h includes EGL which on non-arm includes Xlib #include typedef struct hwc_display_contents_1 hwc_display_contents_1_t; typedef struct hwc_layer_1 hwc_layer_1_t; typedef struct hwc_composer_device_1 hwc_composer_device_1_t; struct light_device_t; class HWComposerNativeWindowBuffer; namespace KWin { class HwcomposerWindow; class BacklightInputEventFilter; -class HwcomposerBackend : public AbstractBackend +class HwcomposerBackend : public Platform { Q_OBJECT - Q_INTERFACES(KWin::AbstractBackend) - Q_PLUGIN_METADATA(IID "org.kde.kwin.AbstractBackend" FILE "hwcomposer.json") + Q_INTERFACES(KWin::Platform) + Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "hwcomposer.json") public: explicit HwcomposerBackend(QObject *parent = nullptr); virtual ~HwcomposerBackend(); void init() override; Screens *createScreens(QObject *parent = nullptr) override; OpenGLBackend *createOpenGLBackend() override; QSize screenSize() const override { return m_displaySize; } HwcomposerWindow *createSurface(); QSize size() const { return m_displaySize; } hwc_composer_device_1_t *device() const { return m_device; } int refreshRate() const { return m_refreshRate; } void enableVSync(bool enable); void waitVSync(); void wakeVSync(); bool isBacklightOff() const { return m_outputBlank; } Q_SIGNALS: void outputBlankChanged(); private Q_SLOTS: void toggleBlankOutput(); private: void initLights(); void toggleScreenBrightness(); QSize m_displaySize; hwc_composer_device_1_t *m_device = nullptr; light_device_t *m_lights = nullptr; bool m_outputBlank = true; int m_refreshRate = 60000; int m_vsyncInterval = 16; bool m_hasVsync = false; QMutex m_vsyncMutex; QWaitCondition m_vsyncWaitCondition; QScopedPointer m_filter; }; class HwcomposerWindow : public HWComposerNativeWindow { public: virtual ~HwcomposerWindow(); void present(HWComposerNativeWindowBuffer *buffer); private: friend HwcomposerBackend; HwcomposerWindow(HwcomposerBackend *backend); HwcomposerBackend *m_backend; hwc_display_contents_1_t **m_list; }; class BacklightInputEventFilter : public InputEventFilter { public: BacklightInputEventFilter(HwcomposerBackend *backend); virtual ~BacklightInputEventFilter(); bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override; bool wheelEvent(QWheelEvent *event) override; bool keyEvent(QKeyEvent *event) override; bool touchDown(quint32 id, const QPointF &pos, quint32 time) override; bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override; bool touchUp(quint32 id, quint32 time) override; private: void toggleBacklight(); HwcomposerBackend *m_backend; QElapsedTimer m_doubleTapTimer; QVector m_touchPoints; bool m_secondTap = false; }; } #endif diff --git a/backends/virtual/virtual_backend.cpp b/backends/virtual/virtual_backend.cpp index df5c764aa..b231fbb5e 100644 --- a/backends/virtual/virtual_backend.cpp +++ b/backends/virtual/virtual_backend.cpp @@ -1,84 +1,84 @@ /******************************************************************** 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 "virtual_backend.h" #include "scene_qpainter_virtual_backend.h" #include "screens_virtual.h" #include "wayland_server.h" #include "egl_gbm_backend.h" // Qt #include // KWayland #include namespace KWin { VirtualBackend::VirtualBackend(QObject *parent) - : AbstractBackend(parent) + : Platform(parent) { if (qEnvironmentVariableIsSet("KWIN_WAYLAND_VIRTUAL_SCREENSHOTS")) { m_screenshotDir.reset(new QTemporaryDir); if (!m_screenshotDir->isValid()) { m_screenshotDir.reset(); } if (!m_screenshotDir.isNull()) { qDebug() << "Screenshots saved to: " << m_screenshotDir->path(); } } setSoftWareCursor(true); setSupportsPointerWarping(true); } VirtualBackend::~VirtualBackend() = default; void VirtualBackend::init() { m_size = initialWindowSize(); setReady(true); waylandServer()->seat()->setHasPointer(true); waylandServer()->seat()->setHasKeyboard(true); waylandServer()->seat()->setHasTouch(true); emit screensQueried(); } QString VirtualBackend::screenshotDirPath() const { if (m_screenshotDir.isNull()) { return QString(); } return m_screenshotDir->path(); } Screens *VirtualBackend::createScreens(QObject *parent) { return new VirtualScreens(this, parent); } QPainterBackend *VirtualBackend::createQPainterBackend() { return new VirtualQPainterBackend(this); } OpenGLBackend *VirtualBackend::createOpenGLBackend() { return new EglGbmBackend(this); } } diff --git a/backends/virtual/virtual_backend.h b/backends/virtual/virtual_backend.h index 254d7d4fc..f0d1e2c7d 100644 --- a/backends/virtual/virtual_backend.h +++ b/backends/virtual/virtual_backend.h @@ -1,76 +1,76 @@ /******************************************************************** 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_VIRTUAL_BACKEND_H #define KWIN_VIRTUAL_BACKEND_H #include "abstract_backend.h" #include #include #include class QTemporaryDir; namespace KWin { -class KWIN_EXPORT VirtualBackend : public AbstractBackend +class KWIN_EXPORT VirtualBackend : public Platform { Q_OBJECT - Q_INTERFACES(KWin::AbstractBackend) - Q_PLUGIN_METADATA(IID "org.kde.kwin.AbstractBackend" FILE "virtual.json") + Q_INTERFACES(KWin::Platform) + Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "virtual.json") Q_PROPERTY(QSize size READ size NOTIFY sizeChanged) public: VirtualBackend(QObject *parent = nullptr); virtual ~VirtualBackend(); void init() override; QSize size() const { return m_size; } int outputCount() const { return m_outputCount; } bool saveFrames() const { return !m_screenshotDir.isNull(); } QString screenshotDirPath() const; Screens *createScreens(QObject *parent = nullptr) override; QPainterBackend* createQPainterBackend() override; OpenGLBackend *createOpenGLBackend() override; Q_INVOKABLE void setOutputCount(int count) { m_outputCount = count; } Q_SIGNALS: void sizeChanged(); private: QSize m_size; int m_outputCount = 1; QScopedPointer m_screenshotDir; }; } #endif diff --git a/backends/wayland/wayland_backend.cpp b/backends/wayland/wayland_backend.cpp index 28dac6589..c70bd00cb 100644 --- a/backends/wayland/wayland_backend.cpp +++ b/backends/wayland/wayland_backend.cpp @@ -1,463 +1,463 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ // own #include "wayland_backend.h" #include // KWin #include "cursor.h" #include "logging.h" #include "main.h" #include "scene_qpainter_wayland_backend.h" #include "screens.h" #include "wayland_server.h" #include "wayland_cursor_theme.h" #if HAVE_WAYLAND_EGL #include "egl_wayland_backend.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Qt #include #include // Wayland #include namespace KWin { namespace Wayland { using namespace KWayland::Client; WaylandSeat::WaylandSeat(wl_seat *seat, WaylandBackend *backend) : QObject(NULL) , m_seat(new Seat(this)) , m_pointer(NULL) , m_keyboard(NULL) , m_touch(nullptr) , m_cursor(NULL) , m_enteredSerial(0) , m_backend(backend) , m_installCursor(false) { m_seat->setup(seat); connect(m_seat, &Seat::hasKeyboardChanged, this, [this](bool hasKeyboard) { if (hasKeyboard) { m_keyboard = m_seat->createKeyboard(this); connect(m_keyboard, &Keyboard::keyChanged, this, [this](quint32 key, Keyboard::KeyState state, quint32 time) { switch (state) { case Keyboard::KeyState::Pressed: m_backend->keyboardKeyPressed(key, time); break; case Keyboard::KeyState::Released: m_backend->keyboardKeyReleased(key, time); break; default: Q_UNREACHABLE(); } } ); connect(m_keyboard, &Keyboard::modifiersChanged, this, [this](quint32 depressed, quint32 latched, quint32 locked, quint32 group) { m_backend->keyboardModifiers(depressed, latched, locked, group); } ); connect(m_keyboard, &Keyboard::keymapChanged, this, [this](int fd, quint32 size) { m_backend->keymapChange(fd, size); } ); } else { destroyKeyboard(); } } ); connect(m_seat, &Seat::hasPointerChanged, this, [this](bool hasPointer) { if (hasPointer && !m_pointer) { m_pointer = m_seat->createPointer(this); connect(m_pointer, &Pointer::entered, this, [this](quint32 serial) { m_enteredSerial = serial; if (!m_installCursor) { // explicitly hide cursor m_pointer->hideCursor(); } } ); connect(m_pointer, &Pointer::motion, this, [this](const QPointF &relativeToSurface, quint32 time) { m_backend->pointerMotion(relativeToSurface, time); } ); connect(m_pointer, &Pointer::buttonStateChanged, this, [this](quint32 serial, quint32 time, quint32 button, Pointer::ButtonState state) { Q_UNUSED(serial) switch (state) { case Pointer::ButtonState::Pressed: m_backend->pointerButtonPressed(button, time); break; case Pointer::ButtonState::Released: m_backend->pointerButtonReleased(button, time); break; default: Q_UNREACHABLE(); } } ); connect(m_pointer, &Pointer::axisChanged, this, [this](quint32 time, Pointer::Axis axis, qreal delta) { switch (axis) { case Pointer::Axis::Horizontal: m_backend->pointerAxisHorizontal(delta, time); break; case Pointer::Axis::Vertical: m_backend->pointerAxisVertical(delta, time); break; default: Q_UNREACHABLE(); } } ); } else { destroyPointer(); } } ); connect(m_seat, &Seat::hasTouchChanged, [this] (bool hasTouch) { if (hasTouch && !m_touch) { m_touch = m_seat->createTouch(this); - connect(m_touch, &Touch::sequenceCanceled, m_backend, &AbstractBackend::touchCancel); - connect(m_touch, &Touch::frameEnded, m_backend, &AbstractBackend::touchFrame); + connect(m_touch, &Touch::sequenceCanceled, m_backend, &Platform::touchCancel); + connect(m_touch, &Touch::frameEnded, m_backend, &Platform::touchFrame); connect(m_touch, &Touch::sequenceStarted, this, [this] (TouchPoint *tp) { m_backend->touchDown(tp->id(), tp->position(), tp->time()); } ); connect(m_touch, &Touch::pointAdded, this, [this] (TouchPoint *tp) { m_backend->touchDown(tp->id(), tp->position(), tp->time()); } ); connect(m_touch, &Touch::pointRemoved, this, [this] (TouchPoint *tp) { m_backend->touchUp(tp->id(), tp->time()); } ); connect(m_touch, &Touch::pointMoved, this, [this] (TouchPoint *tp) { m_backend->touchMotion(tp->id(), tp->position(), tp->time()); } ); } else { destroyTouch(); } } ); WaylandServer *server = waylandServer(); if (server) { using namespace KWayland::Server; SeatInterface *si = server->seat(); connect(m_seat, &Seat::hasKeyboardChanged, si, &SeatInterface::setHasKeyboard); connect(m_seat, &Seat::hasPointerChanged, si, &SeatInterface::setHasPointer); connect(m_seat, &Seat::hasTouchChanged, si, &SeatInterface::setHasTouch); connect(m_seat, &Seat::nameChanged, si, &SeatInterface::setName); } } WaylandSeat::~WaylandSeat() { destroyPointer(); destroyKeyboard(); destroyTouch(); } void WaylandSeat::destroyPointer() { delete m_pointer; m_pointer = nullptr; } void WaylandSeat::destroyKeyboard() { delete m_keyboard; m_keyboard = nullptr; } void WaylandSeat::destroyTouch() { delete m_touch; m_touch = nullptr; } void WaylandSeat::installCursorImage(wl_buffer *image, const QSize &size, const QPoint &hotSpot) { if (!m_installCursor) { return; } if (!m_pointer || !m_pointer->isValid()) { return; } if (!m_cursor) { m_cursor = m_backend->compositor()->createSurface(this); } if (!m_cursor || !m_cursor->isValid()) { return; } m_pointer->setCursor(m_cursor, hotSpot); m_cursor->attachBuffer(image); m_cursor->damage(QRect(QPoint(0,0), size)); m_cursor->commit(Surface::CommitFlag::None); m_backend->flush(); } void WaylandSeat::installCursorImage(const QImage &image, const QPoint &hotSpot) { if (image.isNull()) { installCursorImage(nullptr, QSize(), QPoint()); return; } installCursorImage(*(m_backend->shmPool()->createBuffer(image).data()), image.size(), hotSpot); } void WaylandSeat::setInstallCursor(bool install) { // TODO: remove, add? m_installCursor = install; } WaylandBackend::WaylandBackend(QObject *parent) - : AbstractBackend(parent) + : Platform(parent) , m_display(nullptr) , m_eventQueue(new EventQueue(this)) , m_registry(new Registry(this)) , m_compositor(new Compositor(this)) , m_shell(new Shell(this)) , m_surface(nullptr) , m_shellSurface(NULL) , m_seat() , m_shm(new ShmPool(this)) , m_connectionThreadObject(new ConnectionThread(nullptr)) , m_connectionThread(nullptr) { connect(this, &WaylandBackend::connectionFailed, this, &WaylandBackend::initFailed); connect(this, &WaylandBackend::shellSurfaceSizeChanged, this, &WaylandBackend::screenSizeChanged); } WaylandBackend::~WaylandBackend() { if (m_shellSurface) { m_shellSurface->release(); } if (m_surface) { m_surface->release(); } m_shell->release(); m_compositor->release(); m_registry->release(); m_seat.reset(); m_shm->release(); m_eventQueue->release(); m_connectionThreadObject->deleteLater(); m_connectionThread->quit(); m_connectionThread->wait(); qCDebug(KWIN_WAYLAND_BACKEND) << "Destroyed Wayland display"; } void WaylandBackend::init() { connect(m_registry, &Registry::compositorAnnounced, this, [this](quint32 name) { m_compositor->setup(m_registry->bindCompositor(name, 1)); } ); connect(m_registry, &Registry::shellAnnounced, this, [this](quint32 name) { m_shell->setup(m_registry->bindShell(name, 1)); } ); connect(m_registry, &Registry::seatAnnounced, this, [this](quint32 name) { if (Application::usesLibinput()) { return; } m_seat.reset(new WaylandSeat(m_registry->bindSeat(name, 2), this)); } ); connect(m_registry, &Registry::shmAnnounced, this, [this](quint32 name) { m_shm->setup(m_registry->bindShm(name, 1)); } ); connect(m_registry, &Registry::interfacesAnnounced, this, &WaylandBackend::createSurface); if (!deviceIdentifier().isEmpty()) { m_connectionThreadObject->setSocketName(deviceIdentifier()); } connect(this, &WaylandBackend::cursorChanged, this, [this] { if (m_seat.isNull() || !m_seat->isInstallCursor()) { return; } m_seat->installCursorImage(softwareCursor(), softwareCursorHotspot()); markCursorAsRendered(); } ); initConnection(); } void WaylandBackend::initConnection() { connect(m_connectionThreadObject, &ConnectionThread::connected, this, [this]() { // create the event queue for the main gui thread m_display = m_connectionThreadObject->display(); m_eventQueue->setup(m_connectionThreadObject); m_registry->setEventQueue(m_eventQueue); // setup registry m_registry->create(m_display); m_registry->setup(); }, Qt::QueuedConnection); connect(m_connectionThreadObject, &ConnectionThread::connectionDied, this, [this]() { setReady(false); emit systemCompositorDied(); m_seat.reset(); m_shm->destroy(); if (m_shellSurface) { m_shellSurface->destroy(); delete m_shellSurface; m_shellSurface = nullptr; } if (m_surface) { m_surface->destroy(); delete m_surface; m_surface = nullptr; } if (m_shell) { m_shell->destroy(); } m_compositor->destroy(); m_registry->destroy(); m_eventQueue->destroy(); if (m_display) { m_display = nullptr; } }, Qt::QueuedConnection); connect(m_connectionThreadObject, &ConnectionThread::failed, this, &WaylandBackend::connectionFailed, Qt::QueuedConnection); m_connectionThread = new QThread(this); m_connectionThreadObject->moveToThread(m_connectionThread); m_connectionThread->start(); m_connectionThreadObject->initConnection(); } void WaylandBackend::createSurface() { m_surface = m_compositor->createSurface(this); if (!m_surface || !m_surface->isValid()) { qCCritical(KWIN_WAYLAND_BACKEND) << "Creating Wayland Surface failed"; return; } using namespace KWayland::Client; auto iface = m_registry->interface(Registry::Interface::ServerSideDecorationManager); if (iface.name != 0) { auto manager = m_registry->createServerSideDecorationManager(iface.name, iface.version, this); auto decoration = manager->create(m_surface, this); connect(decoration, &ServerSideDecoration::modeChanged, this, [this, decoration] { if (decoration->mode() != ServerSideDecoration::Mode::Server) { decoration->requestMode(ServerSideDecoration::Mode::Server); } } ); } if (m_seat) { m_seat->setInstallCursor(true); } if (m_shell->isValid()) { m_shellSurface = m_shell->createSurface(m_surface, this); connect(m_shellSurface, &ShellSurface::sizeChanged, this, &WaylandBackend::shellSurfaceSizeChanged); m_shellSurface->setSize(initialWindowSize()); m_shellSurface->setToplevel(); setReady(true); emit screensQueried(); } } QSize WaylandBackend::shellSurfaceSize() const { if (m_shellSurface) { return m_shellSurface->size(); } return QSize(); } Screens *WaylandBackend::createScreens(QObject *parent) { return new BasicScreens(this, parent); } OpenGLBackend *WaylandBackend::createOpenGLBackend() { #if HAVE_WAYLAND_EGL return new EglWaylandBackend(this); #else return nullptr; #endif } QPainterBackend *WaylandBackend::createQPainterBackend() { return new WaylandQPainterBackend(this); } void WaylandBackend::flush() { if (m_connectionThreadObject) { m_connectionThreadObject->flush(); } } } } // KWin diff --git a/backends/wayland/wayland_backend.h b/backends/wayland/wayland_backend.h index 4b2052d56..36ac9f5df 100644 --- a/backends/wayland/wayland_backend.h +++ b/backends/wayland/wayland_backend.h @@ -1,175 +1,175 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_WAYLAND_BACKEND_H #define KWIN_WAYLAND_BACKEND_H // KWin #include "abstract_backend.h" #include #include // Qt #include #include #include #include #include class QTemporaryFile; struct wl_buffer; struct wl_display; struct wl_event_queue; struct wl_seat; namespace KWayland { namespace Client { class Buffer; class ShmPool; class Compositor; class ConnectionThread; class EventQueue; class Keyboard; class Pointer; class Registry; class Seat; class Shell; class ShellSurface; class Surface; class Touch; } } namespace KWin { class WaylandCursorTheme; namespace Wayland { class WaylandBackend; class WaylandSeat; class WaylandSeat : public QObject { Q_OBJECT public: WaylandSeat(wl_seat *seat, WaylandBackend *backend); virtual ~WaylandSeat(); void installCursorImage(wl_buffer *image, const QSize &size, const QPoint &hotspot); void installCursorImage(const QImage &image, const QPoint &hotspot); void setInstallCursor(bool install); bool isInstallCursor() const { return m_installCursor; } private: void destroyPointer(); void destroyKeyboard(); void destroyTouch(); KWayland::Client::Seat *m_seat; KWayland::Client::Pointer *m_pointer; KWayland::Client::Keyboard *m_keyboard; KWayland::Client::Touch *m_touch; KWayland::Client::Surface *m_cursor; uint32_t m_enteredSerial; WaylandBackend *m_backend; bool m_installCursor; }; /** * @brief Class encapsulating all Wayland data structures needed by the Egl backend. * * It creates the connection to the Wayland Compositor, sets up the registry and creates * the Wayland surface and its shell mapping. */ -class KWIN_EXPORT WaylandBackend : public AbstractBackend +class KWIN_EXPORT WaylandBackend : public Platform { Q_OBJECT - Q_INTERFACES(KWin::AbstractBackend) - Q_PLUGIN_METADATA(IID "org.kde.kwin.AbstractBackend" FILE "wayland.json") + Q_INTERFACES(KWin::Platform) + Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "wayland.json") public: explicit WaylandBackend(QObject *parent = nullptr); virtual ~WaylandBackend(); void init() override; wl_display *display(); KWayland::Client::Compositor *compositor(); KWayland::Client::ShmPool *shmPool(); KWayland::Client::Surface *surface() const; QSize shellSurfaceSize() const; Screens *createScreens(QObject *parent = nullptr) override; OpenGLBackend *createOpenGLBackend() override; QPainterBackend *createQPainterBackend() override; QSize screenSize() const override { return shellSurfaceSize(); } void flush(); Q_SIGNALS: void shellSurfaceSizeChanged(const QSize &size); void systemCompositorDied(); void connectionFailed(); private: void initConnection(); void createSurface(); wl_display *m_display; KWayland::Client::EventQueue *m_eventQueue; KWayland::Client::Registry *m_registry; KWayland::Client::Compositor *m_compositor; KWayland::Client::Shell *m_shell; KWayland::Client::Surface *m_surface; KWayland::Client::ShellSurface *m_shellSurface; QScopedPointer m_seat; KWayland::Client::ShmPool *m_shm; KWayland::Client::ConnectionThread *m_connectionThreadObject; QThread *m_connectionThread; }; inline wl_display *WaylandBackend::display() { return m_display; } inline KWayland::Client::Compositor *WaylandBackend::compositor() { return m_compositor; } inline KWayland::Client::ShmPool* WaylandBackend::shmPool() { return m_shm; } inline KWayland::Client::Surface *WaylandBackend::surface() const { return m_surface; } } // namespace Wayland } // namespace KWin #endif // KWIN_WAYLAND_BACKEND_H diff --git a/backends/x11/x11windowed_backend.cpp b/backends/x11/x11windowed_backend.cpp index 971f5e0d3..3fe5c6297 100644 --- a/backends/x11/x11windowed_backend.cpp +++ b/backends/x11/x11windowed_backend.cpp @@ -1,456 +1,456 @@ /******************************************************************** 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 "x11windowed_backend.h" #include "scene_qpainter_x11_backend.h" #include "logging.h" #include "wayland_server.h" #include "xcbutils.h" #include "egl_x11_backend.h" #include "screens.h" #include // KDE #include #include #include #include // kwayland #include #include #include #include // xcb #include // system #include #include namespace KWin { X11WindowedBackend::X11WindowedBackend(QObject *parent) - : AbstractBackend(parent) + : Platform(parent) { setSupportsPointerWarping(true); connect(this, &X11WindowedBackend::sizeChanged, this, &X11WindowedBackend::screenSizeChanged); } X11WindowedBackend::~X11WindowedBackend() { if (m_connection) { if (m_keySymbols) { xcb_key_symbols_free(m_keySymbols); } for (auto it = m_windows.begin(); it != m_windows.end(); ++it) { xcb_unmap_window(m_connection, (*it).window); xcb_destroy_window(m_connection, (*it).window); delete (*it).winInfo; } if (m_cursor) { xcb_free_cursor(m_connection, m_cursor); } xcb_disconnect(m_connection); } } void X11WindowedBackend::init() { int screen = 0; xcb_connection_t *c = nullptr; Display *xDisplay = XOpenDisplay(deviceIdentifier().constData()); if (xDisplay) { c = XGetXCBConnection(xDisplay); XSetEventQueueOwner(xDisplay, XCBOwnsEventQueue); screen = XDefaultScreen(xDisplay); } if (c && !xcb_connection_has_error(c)) { m_connection = c; m_screenNumber = screen; m_display = xDisplay; for (xcb_screen_iterator_t it = xcb_setup_roots_iterator(xcb_get_setup(m_connection)); it.rem; --screen, xcb_screen_next(&it)) { if (screen == m_screenNumber) { m_screen = it.data; } } XRenderUtils::init(m_connection, m_screen->root); createWindow(); startEventReading(); connect(this, &X11WindowedBackend::cursorChanged, this, [this] { createCursor(softwareCursor(), softwareCursorHotspot()); } ); setReady(true); waylandServer()->seat()->setHasPointer(true); waylandServer()->seat()->setHasKeyboard(true); emit screensQueried(); } else { emit initFailed(); } } void X11WindowedBackend::createWindow() { Xcb::Atom protocolsAtom(QByteArrayLiteral("WM_PROTOCOLS"), false, m_connection); Xcb::Atom deleteWindowAtom(QByteArrayLiteral("WM_DELETE_WINDOW"), false, m_connection); for (int i = 0; i < initialOutputCount(); ++i) { Output o; o.window = xcb_generate_id(m_connection); uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; const uint32_t values[] = { m_screen->black_pixel, XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_EXPOSURE }; o.size = initialWindowSize(); if (!m_windows.isEmpty()) { const auto &p = m_windows.last(); o.internalPosition = QPoint(p.internalPosition.x() + p.size.width(), 0); } xcb_create_window(m_connection, XCB_COPY_FROM_PARENT, o.window, m_screen->root, 0, 0, o.size.width(), o.size.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, mask, values); o.winInfo = new NETWinInfo(m_connection, o.window, m_screen->root, NET::WMWindowType, NET::Properties2()); o.winInfo->setWindowType(NET::Normal); o.winInfo->setPid(QCoreApplication::applicationPid()); QIcon windowIcon = QIcon::fromTheme(QStringLiteral("kwin")); auto addIcon = [&o, &windowIcon] (const QSize &size) { if (windowIcon.actualSize(size) != size) { return; } NETIcon icon; icon.data = windowIcon.pixmap(size).toImage().bits(); icon.size.width = size.width(); icon.size.height = size.height(); o.winInfo->setIcon(icon, false); }; addIcon(QSize(16, 16)); addIcon(QSize(32, 32)); addIcon(QSize(48, 48)); xcb_map_window(m_connection, o.window); m_protocols = protocolsAtom; m_deleteWindowProtocol = deleteWindowAtom; xcb_change_property(m_connection, XCB_PROP_MODE_REPLACE, o.window, m_protocols, XCB_ATOM_ATOM, 32, 1, &m_deleteWindowProtocol); m_windows << o; } updateWindowTitle(); xcb_flush(m_connection); } void X11WindowedBackend::startEventReading() { QSocketNotifier *notifier = new QSocketNotifier(xcb_get_file_descriptor(m_connection), QSocketNotifier::Read, this); auto processXcbEvents = [this] { while (auto event = xcb_poll_for_event(m_connection)) { handleEvent(event); free(event); } xcb_flush(m_connection); }; connect(notifier, &QSocketNotifier::activated, this, processXcbEvents); connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, processXcbEvents); connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::awake, this, processXcbEvents); } void X11WindowedBackend::handleEvent(xcb_generic_event_t *e) { const uint8_t eventType = e->response_type & ~0x80; switch (eventType) { case XCB_BUTTON_PRESS: case XCB_BUTTON_RELEASE: handleButtonPress(reinterpret_cast(e)); break; case XCB_MOTION_NOTIFY: { auto event = reinterpret_cast(e); auto it = std::find_if(m_windows.constBegin(), m_windows.constEnd(), [event] (const Output &o) { return o.window == event->event; }); if (it == m_windows.constEnd()) { break; } pointerMotion(QPointF(event->root_x - (*it).xPosition.x() + (*it).internalPosition.x(), event->root_y - (*it).xPosition.y() + (*it).internalPosition.y()), event->time); } break; case XCB_KEY_PRESS: case XCB_KEY_RELEASE: { auto event = reinterpret_cast(e); if (eventType == XCB_KEY_PRESS) { if (!m_keySymbols) { m_keySymbols = xcb_key_symbols_alloc(m_connection); } const xcb_keysym_t kc = xcb_key_symbols_get_keysym(m_keySymbols, event->detail, 0); if (kc == XK_Control_R) { grabKeyboard(event->time); } keyboardKeyPressed(event->detail - 8, event->time); } else { keyboardKeyReleased(event->detail - 8, event->time); } } break; case XCB_CONFIGURE_NOTIFY: updateSize(reinterpret_cast(e)); break; case XCB_ENTER_NOTIFY: { auto event = reinterpret_cast(e); auto it = std::find_if(m_windows.constBegin(), m_windows.constEnd(), [event] (const Output &o) { return o.window == event->event; }); if (it == m_windows.constEnd()) { break; } pointerMotion(QPointF(event->root_x - (*it).xPosition.x() + (*it).internalPosition.x(), event->root_y - (*it).xPosition.y() + (*it).internalPosition.y()), event->time); } break; case XCB_CLIENT_MESSAGE: handleClientMessage(reinterpret_cast(e)); break; case XCB_EXPOSE: handleExpose(reinterpret_cast(e)); break; case XCB_MAPPING_NOTIFY: if (m_keySymbols) { xcb_refresh_keyboard_mapping(m_keySymbols, reinterpret_cast(e)); } break; default: break; } } void X11WindowedBackend::grabKeyboard(xcb_timestamp_t time) { const bool oldState = m_keyboardGrabbed; if (m_keyboardGrabbed) { xcb_ungrab_keyboard(m_connection, time); xcb_ungrab_pointer(m_connection, time); m_keyboardGrabbed = false; } else { const auto c = xcb_grab_keyboard_unchecked(m_connection, false, window(), time, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); ScopedCPointer grab(xcb_grab_keyboard_reply(m_connection, c, nullptr)); if (grab.isNull()) { return; } if (grab->status == XCB_GRAB_STATUS_SUCCESS) { const auto c = xcb_grab_pointer_unchecked(m_connection, false, window(), XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, window(), XCB_CURSOR_NONE, time); ScopedCPointer grab(xcb_grab_pointer_reply(m_connection, c, nullptr)); if (grab.isNull() || grab->status != XCB_GRAB_STATUS_SUCCESS) { xcb_ungrab_keyboard(m_connection, time); return; } m_keyboardGrabbed = true; } } if (oldState != m_keyboardGrabbed) { updateWindowTitle(); xcb_flush(m_connection); } } void X11WindowedBackend::updateWindowTitle() { const QString grab = m_keyboardGrabbed ? i18n("Press right control to ungrab input") : i18n("Press right control key to grab input"); const QString title = QStringLiteral("%1 (%2) - %3").arg(i18n("KDE Wayland Compositor")) .arg(waylandServer()->display()->socketName()) .arg(grab); for (auto it = m_windows.constBegin(); it != m_windows.constEnd(); ++it) { (*it).winInfo->setName(title.toUtf8().constData()); } } void X11WindowedBackend::handleClientMessage(xcb_client_message_event_t *event) { auto it = std::find_if(m_windows.begin(), m_windows.end(), [event] (const Output &o) { return o.window == event->window; }); if (it == m_windows.end()) { return; } if (event->type == m_protocols && m_protocols != XCB_ATOM_NONE) { if (event->data.data32[0] == m_deleteWindowProtocol && m_deleteWindowProtocol != XCB_ATOM_NONE) { qCDebug(KWIN_X11WINDOWED) << "Backend window is going to be closed, shutting down."; QCoreApplication::quit(); } } } void X11WindowedBackend::handleButtonPress(xcb_button_press_event_t *event) { auto it = std::find_if(m_windows.constBegin(), m_windows.constEnd(), [event] (const Output &o) { return o.window == event->event; }); if (it == m_windows.constEnd()) { return; } bool const pressed = (event->response_type & ~0x80) == XCB_BUTTON_PRESS; if (event->detail >= XCB_BUTTON_INDEX_4 && event->detail <= 7) { // wheel if (!pressed) { return; } const int delta = (event->detail == XCB_BUTTON_INDEX_4 || event->detail == 6) ? -1 : 1; static const qreal s_defaultAxisStepDistance = 10.0; if (event->detail > 5) { pointerAxisHorizontal(delta * s_defaultAxisStepDistance, event->time); } else { pointerAxisVertical(delta * s_defaultAxisStepDistance, event->time); } return; } uint32_t button = 0; switch (event->detail) { case XCB_BUTTON_INDEX_1: button = BTN_LEFT; break; case XCB_BUTTON_INDEX_2: button = BTN_MIDDLE; break; case XCB_BUTTON_INDEX_3: button = BTN_RIGHT; break; default: button = event->detail + BTN_LEFT - 1; return; } pointerMotion(QPointF(event->root_x - (*it).xPosition.x() + (*it).internalPosition.x(), event->root_y - (*it).xPosition.y() + (*it).internalPosition.y()), event->time); if (pressed) { pointerButtonPressed(button, event->time); } else { pointerButtonReleased(button, event->time); } } void X11WindowedBackend::handleExpose(xcb_expose_event_t *event) { repaint(QRect(event->x, event->y, event->width, event->height)); } void X11WindowedBackend::updateSize(xcb_configure_notify_event_t *event) { auto it = std::find_if(m_windows.begin(), m_windows.end(), [event] (const Output &o) { return o.window == event->window; }); if (it == m_windows.end()) { return; } (*it).xPosition = QPoint(event->x, event->y); QSize s = QSize(event->width, event->height); if (s != (*it).size) { (*it).size = s; int x = (*it).internalPosition.x() + s.width(); for (; it != m_windows.end(); ++it) { (*it).internalPosition.setX(x); x += (*it).size.width(); } emit sizeChanged(); } } void X11WindowedBackend::createCursor(const QImage &img, const QPoint &hotspot) { const xcb_pixmap_t pix = xcb_generate_id(m_connection); const xcb_gcontext_t gc = xcb_generate_id(m_connection); const xcb_cursor_t cid = xcb_generate_id(m_connection); xcb_create_pixmap(m_connection, 32, pix, m_screen->root, img.width(), img.height()); xcb_create_gc(m_connection, gc, pix, 0, nullptr); xcb_put_image(m_connection, XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc, img.width(), img.height(), 0, 0, 0, 32, img.byteCount(), img.constBits()); XRenderPicture pic(pix, 32); xcb_render_create_cursor(m_connection, cid, pic, hotspot.x(), hotspot.y()); for (auto it = m_windows.constBegin(); it != m_windows.constEnd(); ++it) { xcb_change_window_attributes(m_connection, (*it).window, XCB_CW_CURSOR, &cid); } xcb_free_pixmap(m_connection, pix); xcb_free_gc(m_connection, gc); if (m_cursor) { xcb_free_cursor(m_connection, m_cursor); } m_cursor = cid; xcb_flush(m_connection); markCursorAsRendered(); } xcb_window_t X11WindowedBackend::rootWindow() const { if (!m_screen) { return XCB_WINDOW_NONE; } return m_screen->root; } Screens *X11WindowedBackend::createScreens(QObject *parent) { return new BasicScreens(this, parent); } OpenGLBackend *X11WindowedBackend::createOpenGLBackend() { return new EglX11Backend(this); } QPainterBackend *X11WindowedBackend::createQPainterBackend() { return new X11WindowedQPainterBackend(this); } void X11WindowedBackend::warpPointer(const QPointF &globalPos) { const xcb_window_t w = m_windows.at(0).window; xcb_warp_pointer(m_connection, w, w, 0, 0, 0, 0, globalPos.x(), globalPos.y()); xcb_flush(m_connection); } xcb_window_t X11WindowedBackend::windowForScreen(int screen) const { if (screen > m_windows.count()) { return XCB_WINDOW_NONE; } return m_windows.at(screen).window; } QVector X11WindowedBackend::screenGeometries() const { QVector ret; for (auto it = m_windows.constBegin(); it != m_windows.constEnd(); ++it) { ret << QRect((*it).internalPosition, (*it).size); } return ret; } } diff --git a/backends/x11/x11windowed_backend.h b/backends/x11/x11windowed_backend.h index cfa3400b3..559289185 100644 --- a/backends/x11/x11windowed_backend.h +++ b/backends/x11/x11windowed_backend.h @@ -1,107 +1,107 @@ /******************************************************************** 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_X11WINDOWED_BACKEND_H #define KWIN_X11WINDOWED_BACKEND_H #include "abstract_backend.h" #include #include #include #include struct _XDisplay; typedef struct _XDisplay Display; typedef struct _XCBKeySymbols xcb_key_symbols_t; class NETWinInfo; namespace KWin { -class KWIN_EXPORT X11WindowedBackend : public AbstractBackend +class KWIN_EXPORT X11WindowedBackend : public Platform { Q_OBJECT - Q_INTERFACES(KWin::AbstractBackend) - Q_PLUGIN_METADATA(IID "org.kde.kwin.AbstractBackend" FILE "x11.json") + Q_INTERFACES(KWin::Platform) + Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "x11.json") Q_PROPERTY(QSize size READ screenSize NOTIFY sizeChanged) public: X11WindowedBackend(QObject *parent = nullptr); virtual ~X11WindowedBackend(); void init() override; QVector screenGeometries() const override; xcb_connection_t *connection() const { return m_connection; } int screenNumer() const { return m_screenNumber; } xcb_window_t window() const { return windowForScreen(0); } xcb_window_t windowForScreen(int screen) const; Display *display() const { return m_display; } xcb_window_t rootWindow() const; Screens *createScreens(QObject *parent = nullptr) override; OpenGLBackend *createOpenGLBackend() override; QPainterBackend* createQPainterBackend() override; void warpPointer(const QPointF &globalPos) override; Q_SIGNALS: void sizeChanged(); private: void createWindow(); void startEventReading(); void grabKeyboard(xcb_timestamp_t time); void updateWindowTitle(); void handleEvent(xcb_generic_event_t *event); void handleClientMessage(xcb_client_message_event_t *event); void handleButtonPress(xcb_button_press_event_t *event); void handleExpose(xcb_expose_event_t *event); void updateSize(xcb_configure_notify_event_t *event); void createCursor(const QImage &img, const QPoint &hotspot); xcb_connection_t *m_connection = nullptr; xcb_screen_t *m_screen = nullptr; xcb_key_symbols_t *m_keySymbols = nullptr; int m_screenNumber = 0; struct Output { xcb_window_t window = XCB_WINDOW_NONE; QSize size; QPoint xPosition; QPoint internalPosition; NETWinInfo *winInfo = nullptr; }; QVector m_windows; xcb_atom_t m_protocols = XCB_ATOM_NONE; xcb_atom_t m_deleteWindowProtocol = XCB_ATOM_NONE; xcb_cursor_t m_cursor = XCB_CURSOR_NONE; Display *m_display = nullptr; bool m_keyboardGrabbed = false; }; } #endif diff --git a/composite.cpp b/composite.cpp index 5648c8e76..0b9d33f91 100644 --- a/composite.cpp +++ b/composite.cpp @@ -1,1332 +1,1332 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak 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 "composite.h" #include "dbusinterface.h" #include "utils.h" #include #include "workspace.h" #include "client.h" #include "unmanaged.h" #include "deleted.h" #include "effects.h" #include "overlaywindow.h" #include "scene.h" #include "scene_xrender.h" #include "scene_opengl.h" #include "scene_qpainter.h" #include "screens.h" #include "shadow.h" #include "useractions.h" #include "compositingprefs.h" #include "xcbutils.h" #include "abstract_backend.h" #include "shell_client.h" #include "wayland_server.h" #include "decorations/decoratedclient.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KWin::Compositor::SuspendReason) namespace KWin { extern int currentRefreshRate(); CompositorSelectionOwner::CompositorSelectionOwner(const char *selection) : KSelectionOwner(selection, connection(), rootWindow()), owning(false) { connect (this, SIGNAL(lostOwnership()), SLOT(looseOwnership())); } void CompositorSelectionOwner::looseOwnership() { owning = false; } KWIN_SINGLETON_FACTORY_VARIABLE(Compositor, s_compositor) static inline qint64 milliToNano(int milli) { return qint64(milli) * 1000 * 1000; } static inline qint64 nanoToMilli(int nano) { return nano / (1000*1000); } Compositor::Compositor(QObject* workspace) : QObject(workspace) , m_suspended(options->isUseCompositing() ? NoReasonSuspend : UserSuspend) , cm_selection(NULL) , vBlankInterval(0) , fpsInterval(0) , m_xrrRefreshRate(0) , forceUnredirectCheck(false) , m_finishing(false) , m_timeSinceLastVBlank(0) , m_scene(NULL) , m_bufferSwapPending(false) , m_composeAtSwapCompletion(false) { qRegisterMetaType("Compositor::SuspendReason"); connect(&unredirectTimer, SIGNAL(timeout()), SLOT(delayedCheckUnredirect())); connect(&compositeResetTimer, SIGNAL(timeout()), SLOT(restart())); connect(options, &Options::configChanged, this, &Compositor::slotConfigChanged); connect(options, SIGNAL(unredirectFullscreenChanged()), SLOT(delayedCheckUnredirect())); unredirectTimer.setSingleShot(true); compositeResetTimer.setSingleShot(true); nextPaintReference.invalidate(); // Initialize the timer // 2 sec which should be enough to restart the compositor static const int compositorLostMessageDelay = 2000; m_releaseSelectionTimer.setSingleShot(true); m_releaseSelectionTimer.setInterval(compositorLostMessageDelay); connect(&m_releaseSelectionTimer, SIGNAL(timeout()), SLOT(releaseCompositorSelection())); m_unusedSupportPropertyTimer.setInterval(compositorLostMessageDelay); m_unusedSupportPropertyTimer.setSingleShot(true); connect(&m_unusedSupportPropertyTimer, SIGNAL(timeout()), SLOT(deleteUnusedSupportProperties())); if (kwinApp()->operationMode() != Application::OperationModeX11) { if (kwinApp()->platform()->isReady()) { QMetaObject::invokeMethod(this, "setup", Qt::QueuedConnection); } - connect(kwinApp()->platform(), &AbstractBackend::readyChanged, this, + connect(kwinApp()->platform(), &Platform::readyChanged, this, [this] (bool ready) { if (ready) { setup(); } else { finish(); } }, Qt::QueuedConnection ); connect(kwinApp(), &Application::x11ConnectionAboutToBeDestroyed, this, [this] { delete cm_selection; cm_selection = nullptr; } ); } else { // delay the call to setup by one event cycle // The ctor of this class is invoked from the Workspace ctor, that means before // Workspace is completely constructed, so calling Workspace::self() would result // in undefined behavior. This is fixed by using a delayed invocation. QMetaObject::invokeMethod(this, "setup", Qt::QueuedConnection); } // register DBus new CompositorDBusInterface(this); } Compositor::~Compositor() { emit aboutToDestroy(); finish(); deleteUnusedSupportProperties(); delete cm_selection; s_compositor = NULL; } void Compositor::setup() { if (hasScene()) return; if (m_suspended) { QStringList reasons; if (m_suspended & UserSuspend) { reasons << QStringLiteral("Disabled by User"); } if (m_suspended & BlockRuleSuspend) { reasons << QStringLiteral("Disabled by Window"); } if (m_suspended & ScriptSuspend) { reasons << QStringLiteral("Disabled by Script"); } qCDebug(KWIN_CORE) << "Compositing is suspended, reason:" << reasons; return; } else if (!CompositingPrefs::compositingPossible()) { qCCritical(KWIN_CORE) << "Compositing is not possible"; return; } m_starting = true; if (!options->isCompositingInitialized()) { options->reloadCompositingSettings(true); slotCompositingOptionsInitialized(); } else { slotCompositingOptionsInitialized(); } } extern int screen_number; // main.cpp extern bool is_multihead; void Compositor::slotCompositingOptionsInitialized() { claimCompositorSelection(); // There might still be a deleted around, needs to be cleared before creating the scene (BUG 333275) if (Workspace::self()) { while (!Workspace::self()->deletedList().isEmpty()) { Workspace::self()->deletedList().first()->discard(); } } switch(options->compositingMode()) { case OpenGLCompositing: { qCDebug(KWIN_CORE) << "Initializing OpenGL compositing"; // Some broken drivers crash on glXQuery() so to prevent constant KWin crashes: KSharedConfigPtr unsafeConfigPtr = kwinApp()->config(); KConfigGroup unsafeConfig(unsafeConfigPtr, "Compositing"); const QString openGLIsUnsafe = QLatin1String("OpenGLIsUnsafe") + (is_multihead ? QString::number(screen_number) : QString()); if (unsafeConfig.readEntry(openGLIsUnsafe, false)) qCWarning(KWIN_CORE) << "KWin has detected that your OpenGL library is unsafe to use"; else { unsafeConfig.writeEntry(openGLIsUnsafe, true); unsafeConfig.sync(); if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL && !kwinApp()->shouldUseWaylandForCompositing() && !CompositingPrefs::hasGlx()) { unsafeConfig.writeEntry(openGLIsUnsafe, false); unsafeConfig.sync(); qCDebug(KWIN_CORE) << "No glx extensions available"; break; } m_scene = SceneOpenGL::createScene(this); // TODO: Add 30 second delay to protect against screen freezes as well unsafeConfig.writeEntry(openGLIsUnsafe, false); unsafeConfig.sync(); if (m_scene && !m_scene->initFailed()) { connect(static_cast(m_scene), &SceneOpenGL::resetCompositing, this, &Compositor::restart); break; // --> } delete m_scene; m_scene = NULL; } // Do not Fall back to XRender - it causes problems when selfcheck fails during startup, but works later on break; } #ifdef KWIN_HAVE_XRENDER_COMPOSITING case XRenderCompositing: qCDebug(KWIN_CORE) << "Initializing XRender compositing"; m_scene = SceneXrender::createScene(this); break; #endif case QPainterCompositing: qCDebug(KWIN_CORE) << "Initializing QPainter compositing"; m_scene = SceneQPainter::createScene(this); break; default: qCDebug(KWIN_CORE) << "No compositing enabled"; m_starting = false; if (cm_selection) { cm_selection->owning = false; cm_selection->release(); } if (kwinApp()->requiresCompositing()) { qCCritical(KWIN_CORE) << "The used windowing system requires compositing"; qCCritical(KWIN_CORE) << "We are going to quit KWin now as it is broken"; qApp->quit(); } return; } if (m_scene == NULL || m_scene->initFailed()) { qCCritical(KWIN_CORE) << "Failed to initialize compositing, compositing disabled"; delete m_scene; m_scene = NULL; m_starting = false; if (cm_selection) { cm_selection->owning = false; cm_selection->release(); } if (kwinApp()->requiresCompositing()) { qCCritical(KWIN_CORE) << "The used windowing system requires compositing"; qCCritical(KWIN_CORE) << "We are going to quit KWin now as it is broken"; qApp->quit(); } return; } if (Workspace::self()) { startupWithWorkspace(); } else { connect(kwinApp(), &Application::workspaceCreated, this, &Compositor::startupWithWorkspace); } } void Compositor::claimCompositorSelection() { if (!cm_selection && kwinApp()->x11Connection()) { char selection_name[ 100 ]; sprintf(selection_name, "_NET_WM_CM_S%d", Application::x11ScreenNumber()); cm_selection = new CompositorSelectionOwner(selection_name); connect(cm_selection, SIGNAL(lostOwnership()), SLOT(finish())); } if (!cm_selection) // no X11 yet return; if (!cm_selection->owning) { cm_selection->claim(true); // force claiming cm_selection->owning = true; } } void Compositor::startupWithWorkspace() { if (!m_starting) { return; } Q_ASSERT(m_scene); claimCompositorSelection(); m_xrrRefreshRate = KWin::currentRefreshRate(); fpsInterval = options->maxFpsInterval(); if (m_scene->syncsToVBlank()) { // if we do vsync, set the fps to the next multiple of the vblank rate vBlankInterval = milliToNano(1000) / m_xrrRefreshRate; fpsInterval = qMax((fpsInterval / vBlankInterval) * vBlankInterval, vBlankInterval); } else vBlankInterval = milliToNano(1); // no sync - DO NOT set "0", would cause div-by-zero segfaults. m_timeSinceLastVBlank = fpsInterval - (options->vBlankTime() + 1); // means "start now" - we don't have even a slight idea when the first vsync will occur scheduleRepaint(); xcb_composite_redirect_subwindows(connection(), rootWindow(), XCB_COMPOSITE_REDIRECT_MANUAL); new EffectsHandlerImpl(this, m_scene); // sets also the 'effects' pointer connect(Workspace::self(), &Workspace::deletedRemoved, m_scene, &Scene::windowDeleted); connect(effects, SIGNAL(screenGeometryChanged(QSize)), SLOT(addRepaintFull())); addRepaintFull(); foreach (Client * c, Workspace::self()->clientList()) { c->setupCompositing(); c->getShadow(); } foreach (Client * c, Workspace::self()->desktopList()) c->setupCompositing(); foreach (Unmanaged * c, Workspace::self()->unmanagedList()) { c->setupCompositing(); c->getShadow(); } emit compositingToggled(true); m_starting = false; if (m_releaseSelectionTimer.isActive()) { m_releaseSelectionTimer.stop(); } // render at least once performCompositing(); } void Compositor::scheduleRepaint() { if (!compositeTimer.isActive()) setCompositeTimer(); } void Compositor::finish() { if (!hasScene()) return; m_finishing = true; m_releaseSelectionTimer.start(); if (Workspace::self()) { foreach (Client * c, Workspace::self()->clientList()) m_scene->windowClosed(c, NULL); foreach (Client * c, Workspace::self()->desktopList()) m_scene->windowClosed(c, NULL); foreach (Unmanaged * c, Workspace::self()->unmanagedList()) m_scene->windowClosed(c, NULL); foreach (Deleted * c, Workspace::self()->deletedList()) m_scene->windowDeleted(c); foreach (Client * c, Workspace::self()->clientList()) c->finishCompositing(); foreach (Client * c, Workspace::self()->desktopList()) c->finishCompositing(); foreach (Unmanaged * c, Workspace::self()->unmanagedList()) c->finishCompositing(); foreach (Deleted * c, Workspace::self()->deletedList()) c->finishCompositing(); xcb_composite_unredirect_subwindows(connection(), rootWindow(), XCB_COMPOSITE_REDIRECT_MANUAL); } delete effects; effects = NULL; delete m_scene; m_scene = NULL; compositeTimer.stop(); repaints_region = QRegion(); if (Workspace::self()) { for (ClientList::ConstIterator it = Workspace::self()->clientList().constBegin(); it != Workspace::self()->clientList().constEnd(); ++it) { // forward all opacity values to the frame in case there'll be other CM running if ((*it)->opacity() != 1.0) { NETWinInfo i(connection(), (*it)->frameId(), rootWindow(), 0, 0); i.setOpacity(static_cast< unsigned long >((*it)->opacity() * 0xffffffff)); } } // discard all Deleted windows (#152914) while (!Workspace::self()->deletedList().isEmpty()) Workspace::self()->deletedList().first()->discard(); } m_finishing = false; emit compositingToggled(false); } void Compositor::releaseCompositorSelection() { if (hasScene() && !m_finishing) { // compositor is up and running again, no need to release the selection return; } if (m_starting) { // currently still starting the compositor, it might fail, so restart the timer to test again m_releaseSelectionTimer.start(); return; } if (m_finishing) { // still shutting down, a restart might follow, so restart the timer to test again m_releaseSelectionTimer.start(); return; } qCDebug(KWIN_CORE) << "Releasing compositor selection"; if (cm_selection) { cm_selection->owning = false; cm_selection->release(); } } void Compositor::keepSupportProperty(xcb_atom_t atom) { m_unusedSupportProperties.removeAll(atom); } void Compositor::removeSupportProperty(xcb_atom_t atom) { m_unusedSupportProperties << atom; m_unusedSupportPropertyTimer.start(); } void Compositor::deleteUnusedSupportProperties() { if (m_starting) { // currently still starting the compositor m_unusedSupportPropertyTimer.start(); return; } if (m_finishing) { // still shutting down, a restart might follow m_unusedSupportPropertyTimer.start(); return; } if (kwinApp()->x11Connection()) { foreach (const xcb_atom_t &atom, m_unusedSupportProperties) { // remove property from root window xcb_delete_property(connection(), rootWindow(), atom); } } } // OpenGL self-check failed, fallback to XRender void Compositor::fallbackToXRenderCompositing() { finish(); KConfigGroup config(kwinApp()->config(), "Compositing"); config.writeEntry("Backend", "XRender"); config.sync(); options->setCompositingMode(XRenderCompositing); setup(); } void Compositor::slotConfigChanged() { if (!m_suspended) { setup(); if (effects) // setupCompositing() may fail effects->reconfigure(); addRepaintFull(); } else finish(); } void Compositor::slotReinitialize() { // Reparse config. Config options will be reloaded by setup() kwinApp()->config()->reparseConfiguration(); // Restart compositing finish(); // resume compositing if suspended m_suspended = NoReasonSuspend; options->setCompositingInitialized(false); setup(); if (effects) { // setup() may fail effects->reconfigure(); } } // for the shortcut void Compositor::slotToggleCompositing() { if (kwinApp()->requiresCompositing()) { // we are not allowed to turn on/off compositing return; } if (m_suspended) { // direct user call; clear all bits resume(AllReasonSuspend); } else { // but only set the user one (sufficient to suspend) suspend(UserSuspend); } } void Compositor::updateCompositeBlocking() { updateCompositeBlocking(NULL); } void Compositor::updateCompositeBlocking(Client *c) { if (kwinApp()->requiresCompositing()) { return; } if (c) { // if c == 0 we just check if we can resume if (c->isBlockingCompositing()) { if (!(m_suspended & BlockRuleSuspend)) // do NOT attempt to call suspend(true); from within the eventchain! QMetaObject::invokeMethod(this, "suspend", Qt::QueuedConnection, Q_ARG(Compositor::SuspendReason, BlockRuleSuspend)); } } else if (m_suspended & BlockRuleSuspend) { // lost a client and we're blocked - can we resume? bool resume = true; for (ClientList::ConstIterator it = Workspace::self()->clientList().constBegin(); it != Workspace::self()->clientList().constEnd(); ++it) { if ((*it)->isBlockingCompositing()) { resume = false; break; } } if (resume) { // do NOT attempt to call suspend(false); from within the eventchain! QMetaObject::invokeMethod(this, "resume", Qt::QueuedConnection, Q_ARG(Compositor::SuspendReason, BlockRuleSuspend)); } } } void Compositor::suspend(Compositor::SuspendReason reason) { if (kwinApp()->requiresCompositing()) { return; } Q_ASSERT(reason != NoReasonSuspend); m_suspended |= reason; if (reason & KWin::Compositor::ScriptSuspend) { // when disabled show a shortcut how the user can get back compositing const auto shortcuts = KGlobalAccel::self()->shortcut(workspace()->findChild(QStringLiteral("Suspend Compositing"))); if (!shortcuts.isEmpty()) { // display notification only if there is the shortcut const QString message = i18n("Desktop effects have been suspended by another application.
" "You can resume using the '%1' shortcut.", shortcuts.first().toString(QKeySequence::NativeText)); KNotification::event(QStringLiteral("compositingsuspendeddbus"), message); } } finish(); } void Compositor::resume(Compositor::SuspendReason reason) { Q_ASSERT(reason != NoReasonSuspend); m_suspended &= ~reason; setup(); // signal "toggled" is eventually emitted from within setup } void Compositor::restart() { if (hasScene()) { finish(); QTimer::singleShot(0, this, SLOT(setup())); } } void Compositor::addRepaint(int x, int y, int w, int h) { if (!hasScene()) return; repaints_region += QRegion(x, y, w, h); scheduleRepaint(); } void Compositor::addRepaint(const QRect& r) { if (!hasScene()) return; repaints_region += r; scheduleRepaint(); } void Compositor::addRepaint(const QRegion& r) { if (!hasScene()) return; repaints_region += r; scheduleRepaint(); } void Compositor::addRepaintFull() { if (!hasScene()) return; const QSize &s = screens()->size(); repaints_region = QRegion(0, 0, s.width(), s.height()); scheduleRepaint(); } void Compositor::timerEvent(QTimerEvent *te) { if (te->timerId() == compositeTimer.timerId()) { performCompositing(); } else QObject::timerEvent(te); } void Compositor::aboutToSwapBuffers() { assert(!m_bufferSwapPending); m_bufferSwapPending = true; } void Compositor::bufferSwapComplete() { assert(m_bufferSwapPending); m_bufferSwapPending = false; if (m_composeAtSwapCompletion) { m_composeAtSwapCompletion = false; performCompositing(); } } void Compositor::performCompositing() { if (m_scene->usesOverlayWindow() && !isOverlayWindowVisible()) return; // nothing is visible anyway // If a buffer swap is still pending, we return to the event loop and // continue processing events until the swap has completed. if (m_bufferSwapPending) { m_composeAtSwapCompletion = true; compositeTimer.stop(); return; } // If outputs are disabled, we return to the event loop and // continue processing events until the outputs are enabled again if (waylandServer() && !kwinApp()->platform()->areOutputsEnabled()) { compositeTimer.stop(); return; } // Create a list of all windows in the stacking order ToplevelList windows = Workspace::self()->xStackingOrder(); ToplevelList damaged; // Reset the damage state of each window and fetch the damage region // without waiting for a reply foreach (Toplevel *win, windows) { if (win->resetAndFetchDamage()) damaged << win; } if (damaged.count() > 0) { m_scene->triggerFence(); xcb_flush(connection()); } // Move elevated windows to the top of the stacking order foreach (EffectWindow *c, static_cast(effects)->elevatedWindows()) { Toplevel* t = static_cast< EffectWindowImpl* >(c)->window(); windows.removeAll(t); windows.append(t); } // Get the replies foreach (Toplevel *win, damaged) { // Discard the cached lanczos texture if (win->effectWindow()) { const QVariant texture = win->effectWindow()->data(LanczosCacheRole); if (texture.isValid()) { delete static_cast(texture.value()); win->effectWindow()->setData(LanczosCacheRole, QVariant()); } } win->getDamageRegionReply(); } if (repaints_region.isEmpty() && !windowRepaintsPending()) { m_scene->idle(); m_timeSinceLastVBlank = fpsInterval - (options->vBlankTime() + 1); // means "start now" m_timeSinceStart += m_timeSinceLastVBlank; // Note: It would seem here we should undo suspended unredirect, but when scenes need // it for some reason, e.g. transformations or translucency, the next pass that does not // need this anymore and paints normally will also reset the suspended unredirect. // Otherwise the window would not be painted normally anyway. compositeTimer.stop(); return; } // skip windows that are not yet ready for being painted and if screen is locked skip windows that are // neither lockscreen nor inputmethod windows // TODO ? // this cannot be used so carelessly - needs protections against broken clients, the window // should not get focus before it's displayed, handle unredirected windows properly and so on. foreach (Toplevel *t, windows) { if (!t->readyForPainting()) { windows.removeAll(t); } if (waylandServer() && waylandServer()->isScreenLocked()) { if(!t->isLockScreen() && !t->isInputMethod()) { windows.removeAll(t); } } } QRegion repaints = repaints_region; // clear all repaints, so that post-pass can add repaints for the next repaint repaints_region = QRegion(); m_timeSinceLastVBlank = m_scene->paint(repaints, windows); m_timeSinceStart += m_timeSinceLastVBlank; if (kwinApp()->shouldUseWaylandForCompositing()) { for (Toplevel *win : damaged) { if (auto surface = win->surface()) { surface->frameRendered(m_timeSinceStart); } } } compositeTimer.stop(); // stop here to ensure *we* cause the next repaint schedule - not some effect through m_scene->paint() // Trigger at least one more pass even if there would be nothing to paint, so that scene->idle() // is called the next time. If there would be nothing pending, it will not restart the timer and // scheduleRepaint() would restart it again somewhen later, called from functions that // would again add something pending. if (m_bufferSwapPending && m_scene->syncsToVBlank()) { m_composeAtSwapCompletion = true; } else { scheduleRepaint(); } } bool Compositor::windowRepaintsPending() const { foreach (Toplevel * c, Workspace::self()->clientList()) if (!c->repaints().isEmpty()) return true; foreach (Toplevel * c, Workspace::self()->desktopList()) if (!c->repaints().isEmpty()) return true; foreach (Toplevel * c, Workspace::self()->unmanagedList()) if (!c->repaints().isEmpty()) return true; foreach (Toplevel * c, Workspace::self()->deletedList()) if (!c->repaints().isEmpty()) return true; if (auto w = waylandServer()) { const auto &clients = w->clients(); for (auto c : clients) { if (c->isShown(true) && !c->repaints().isEmpty()) { return true; } } const auto &internalClients = w->internalClients(); for (auto c : internalClients) { if (c->isShown(true) && !c->repaints().isEmpty()) { return true; } } } return false; } void Compositor::setCompositeResetTimer(int msecs) { compositeResetTimer.start(msecs); } void Compositor::setCompositeTimer() { if (!hasScene()) // should not really happen, but there may be e.g. some damage events still pending return; if (m_starting || !Workspace::self()) { return; } // Don't start the timer if we're waiting for a swap event if (m_bufferSwapPending && m_composeAtSwapCompletion) return; // Don't start the timer if all outputs are disabled if (waylandServer() && !kwinApp()->platform()->areOutputsEnabled()) { return; } uint waitTime = 1; if (m_scene->blocksForRetrace()) { // TODO: make vBlankTime dynamic?! // It's required because glXWaitVideoSync will *likely* block a full frame if one enters // a retrace pass which can last a variable amount of time, depending on the actual screen // Now, my ooold 19" CRT can do such retrace so that 2ms are entirely sufficient, // while another ooold 15" TFT requires about 6ms qint64 padding = m_timeSinceLastVBlank; if (padding > fpsInterval) { // we're at low repaints or spent more time in painting than the user wanted to wait for that frame padding = vBlankInterval - (padding%vBlankInterval); // -> align to next vblank } else { // -> align to the next maxFps tick padding = ((vBlankInterval - padding%vBlankInterval) + (fpsInterval/vBlankInterval-1)*vBlankInterval); // "remaining time of the first vsync" + "time for the other vsyncs of the frame" } if (padding < options->vBlankTime()) { // we'll likely miss this frame waitTime = nanoToMilli(padding + vBlankInterval - options->vBlankTime()); // so we add one } else { waitTime = nanoToMilli(padding - options->vBlankTime()); } } else { // w/o blocking vsync we just jump to the next demanded tick if (fpsInterval > m_timeSinceLastVBlank) { waitTime = nanoToMilli(fpsInterval - m_timeSinceLastVBlank); if (!waitTime) { waitTime = 1; // will ensure we don't block out the eventloop - the system's just not faster ... } }/* else if (m_scene->syncsToVBlank() && m_timeSinceLastVBlank - fpsInterval < (vBlankInterval<<1)) { // NOTICE - "for later" ------------------------------------------------------------------ // It can happen that we push two frames within one refresh cycle. // Swapping will then block even with triple buffering when the GPU does not discard but // queues frames // now here's the mean part: if we take that as "OMG, we're late - next frame ASAP", // there'll immediately be 2 frames in the pipe, swapping will block, we think we're // late ... ewww // so instead we pad to the clock again and add 2ms safety to ensure the pipe is really // free // NOTICE: obviously m_timeSinceLastVBlank can be too big because we're too slow as well // So if this code was enabled, we'd needlessly half the framerate once more (15 instead of 30) waitTime = nanoToMilli(vBlankInterval - (m_timeSinceLastVBlank - fpsInterval)%vBlankInterval) + 2; }*/ else { waitTime = 1; // ... "0" would be sufficient, but the compositor isn't the WMs only task } } compositeTimer.start(qMin(waitTime, 250u), this); // force 4fps minimum } bool Compositor::isActive() { return !m_finishing && hasScene(); } void Compositor::checkUnredirect() { checkUnredirect(false); } // force is needed when the list of windows changes (e.g. a window goes away) void Compositor::checkUnredirect(bool force) { if (!hasScene() || !m_scene->overlayWindow() || m_scene->overlayWindow()->window() == None || !options->isUnredirectFullscreen()) return; if (force) forceUnredirectCheck = true; if (!unredirectTimer.isActive()) unredirectTimer.start(0); } void Compositor::delayedCheckUnredirect() { if (!hasScene() || !m_scene->overlayWindow() || m_scene->overlayWindow()->window() == None || !(options->isUnredirectFullscreen() || sender() == options)) return; ToplevelList list; bool changed = forceUnredirectCheck; foreach (Client * c, Workspace::self()->clientList()) list.append(c); foreach (Unmanaged * c, Workspace::self()->unmanagedList()) list.append(c); foreach (Toplevel * c, list) { if (c->updateUnredirectedState()) { changed = true; break; } } // no desktops, no Deleted ones if (!changed) return; forceUnredirectCheck = false; // Cut out parts from the overlay window where unredirected windows are, // so that they are actually visible. const QSize &s = screens()->size(); QRegion reg(0, 0, s.width(), s.height()); foreach (Toplevel * c, list) { if (c->unredirected()) reg -= c->geometry(); } m_scene->overlayWindow()->setShape(reg); addRepaint(reg); } bool Compositor::checkForOverlayWindow(WId w) const { if (!hasScene()) { // no scene, so it cannot be the overlay window return false; } if (!m_scene->overlayWindow()) { // no overlay window, it cannot be the overlay return false; } // and compare the window ID's return w == m_scene->overlayWindow()->window(); } WId Compositor::overlayWindow() const { if (!hasScene()) { return None; } if (!m_scene->overlayWindow()) { return None; } return m_scene->overlayWindow()->window(); } bool Compositor::isOverlayWindowVisible() const { if (!hasScene()) { return false; } if (!m_scene->overlayWindow()) { return false; } return m_scene->overlayWindow()->isVisible(); } void Compositor::setOverlayWindowVisibility(bool visible) { if (hasScene() && m_scene->overlayWindow()) { m_scene->overlayWindow()->setVisibility(visible); } } /***************************************************** * Workspace ****************************************************/ bool Workspace::compositing() const { return m_compositor && m_compositor->hasScene(); } //**************************************** // Toplevel //**************************************** bool Toplevel::setupCompositing() { if (!compositing()) return false; if (damage_handle != XCB_NONE) return false; if (kwinApp()->operationMode() == Application::OperationModeX11) { damage_handle = xcb_generate_id(connection()); xcb_damage_create(connection(), damage_handle, frameId(), XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY); } damage_region = QRegion(0, 0, width(), height()); effect_window = new EffectWindowImpl(this); unredirect = false; Compositor::self()->checkUnredirect(true); Compositor::self()->scene()->windowAdded(this); // With unmanaged windows there is a race condition between the client painting the window // and us setting up damage tracking. If the client wins we won't get a damage event even // though the window has been painted. To avoid this we mark the whole window as damaged // and schedule a repaint immediately after creating the damage object. if (dynamic_cast(this)) addDamageFull(); return true; } void Toplevel::finishCompositing(ReleaseReason releaseReason) { if (kwinApp()->operationMode() == Application::OperationModeX11 && damage_handle == XCB_NONE) return; Compositor::self()->checkUnredirect(true); if (effect_window->window() == this) { // otherwise it's already passed to Deleted, don't free data discardWindowPixmap(); delete effect_window; } if (kwinApp()->operationMode() == Application::OperationModeX11 && releaseReason != ReleaseReason::Destroyed) { xcb_damage_destroy(connection(), damage_handle); } damage_handle = XCB_NONE; damage_region = QRegion(); repaints_region = QRegion(); effect_window = NULL; } void Toplevel::discardWindowPixmap() { addDamageFull(); if (effectWindow() != NULL && effectWindow()->sceneWindow() != NULL) effectWindow()->sceneWindow()->pixmapDiscarded(); } void Toplevel::damageNotifyEvent() { m_isDamaged = true; // Note: The rect is supposed to specify the damage extents, // but we don't know it at this point. No one who connects // to this signal uses the rect however. emit damaged(this, QRect()); } bool Toplevel::compositing() const { return Workspace::self()->compositing(); } void Client::damageNotifyEvent() { if (syncRequest.isPending && isResize()) { emit damaged(this, QRect()); m_isDamaged = true; return; } if (!ready_for_painting) { // avoid "setReadyForPainting()" function calling overhead if (syncRequest.counter == XCB_NONE) { // cannot detect complete redraw, consider done now setReadyForPainting(); setupWindowManagementInterface(); } } Toplevel::damageNotifyEvent(); } bool Toplevel::resetAndFetchDamage() { if (!m_isDamaged) return false; if (kwinApp()->operationMode() != Application::OperationModeX11) { m_isDamaged = false; return true; } xcb_connection_t *conn = connection(); // Create a new region and copy the damage region to it, // resetting the damaged state. xcb_xfixes_region_t region = xcb_generate_id(conn); xcb_xfixes_create_region(conn, region, 0, 0); xcb_damage_subtract(conn, damage_handle, 0, region); // Send a fetch-region request and destroy the region m_regionCookie = xcb_xfixes_fetch_region_unchecked(conn, region); xcb_xfixes_destroy_region(conn, region); m_isDamaged = false; m_damageReplyPending = true; return m_damageReplyPending; } void Toplevel::getDamageRegionReply() { if (!m_damageReplyPending) return; m_damageReplyPending = false; // Get the fetch-region reply xcb_xfixes_fetch_region_reply_t *reply = xcb_xfixes_fetch_region_reply(connection(), m_regionCookie, 0); if (!reply) return; // Convert the reply to a QRegion int count = xcb_xfixes_fetch_region_rectangles_length(reply); QRegion region; if (count > 1 && count < 16) { xcb_rectangle_t *rects = xcb_xfixes_fetch_region_rectangles(reply); QVector qrects; qrects.reserve(count); for (int i = 0; i < count; i++) qrects << QRect(rects[i].x, rects[i].y, rects[i].width, rects[i].height); region.setRects(qrects.constData(), count); } else region += QRect(reply->extents.x, reply->extents.y, reply->extents.width, reply->extents.height); damage_region += region; repaints_region += region; free(reply); } void Toplevel::addDamageFull() { if (!compositing()) return; damage_region = rect(); repaints_region |= rect(); emit damaged(this, rect()); } void Toplevel::resetDamage() { damage_region = QRegion(); } void Toplevel::addRepaint(const QRect& r) { if (!compositing()) { return; } repaints_region += r; emit needsRepaint(); } void Toplevel::addRepaint(int x, int y, int w, int h) { QRect r(x, y, w, h); addRepaint(r); } void Toplevel::addRepaint(const QRegion& r) { if (!compositing()) { return; } repaints_region += r; emit needsRepaint(); } void Toplevel::addLayerRepaint(const QRect& r) { if (!compositing()) { return; } layer_repaints_region += r; emit needsRepaint(); } void Toplevel::addLayerRepaint(int x, int y, int w, int h) { QRect r(x, y, w, h); addLayerRepaint(r); } void Toplevel::addLayerRepaint(const QRegion& r) { if (!compositing()) return; layer_repaints_region += r; emit needsRepaint(); } void Toplevel::addRepaintFull() { repaints_region = visibleRect().translated(-pos()); emit needsRepaint(); } void Toplevel::resetRepaints() { repaints_region = QRegion(); layer_repaints_region = QRegion(); } void Toplevel::addWorkspaceRepaint(int x, int y, int w, int h) { addWorkspaceRepaint(QRect(x, y, w, h)); } void Toplevel::addWorkspaceRepaint(const QRect& r2) { if (!compositing()) return; Compositor::self()->addRepaint(r2); } bool Toplevel::updateUnredirectedState() { assert(compositing()); bool should = options->isUnredirectFullscreen() && shouldUnredirect() && !unredirectSuspend && !shape() && !hasAlpha() && opacity() == 1.0 && !static_cast(effects)->activeFullScreenEffect(); if (should == unredirect) return false; static QElapsedTimer lastUnredirect; static const qint64 msecRedirectInterval = 100; if (!lastUnredirect.hasExpired(msecRedirectInterval)) { QTimer::singleShot(msecRedirectInterval, Compositor::self(), SLOT(checkUnredirect())); return false; } lastUnredirect.start(); unredirect = should; if (unredirect) { qCDebug(KWIN_CORE) << "Unredirecting:" << this; xcb_composite_unredirect_window(connection(), frameId(), XCB_COMPOSITE_REDIRECT_MANUAL); } else { qCDebug(KWIN_CORE) << "Redirecting:" << this; xcb_composite_redirect_window(connection(), frameId(), XCB_COMPOSITE_REDIRECT_MANUAL); discardWindowPixmap(); } return true; } void Toplevel::suspendUnredirect(bool suspend) { if (unredirectSuspend == suspend) return; unredirectSuspend = suspend; Compositor::self()->checkUnredirect(); } //**************************************** // Client //**************************************** bool Client::setupCompositing() { if (!Toplevel::setupCompositing()){ return false; } if (isDecorated()) { decoratedClient()->destroyRenderer(); } updateVisibility(); // for internalKeep() return true; } void Client::finishCompositing(ReleaseReason releaseReason) { Toplevel::finishCompositing(releaseReason); updateVisibility(); if (!deleting) { if (isDecorated()) { decoratedClient()->destroyRenderer(); } } // for safety in case KWin is just resizing the window resetHaveResizeEffect(); } bool Client::shouldUnredirect() const { if (isActiveFullScreen()) { ToplevelList stacking = workspace()->xStackingOrder(); for (int pos = stacking.count() - 1; pos >= 0; --pos) { Toplevel* c = stacking.at(pos); if (c == this) // is not covered by any other window, ok to unredirect return true; if (c->geometry().intersects(geometry())) return false; } abort(); } return false; } //**************************************** // Unmanaged //**************************************** bool Unmanaged::shouldUnredirect() const { // the pixmap is needed for the login effect, a nicer solution would be the login effect increasing // refcount for the window pixmap (which would prevent unredirect), avoiding this hack if (resourceClass() == "ksplashx" || resourceClass() == "ksplashsimple" || resourceClass() == "ksplashqml" ) return false; // it must cover whole display or one xinerama screen, and be the topmost there const int desktop = VirtualDesktopManager::self()->current(); if (geometry() == workspace()->clientArea(FullArea, geometry().center(), desktop) || geometry() == workspace()->clientArea(ScreenArea, geometry().center(), desktop)) { ToplevelList stacking = workspace()->xStackingOrder(); for (int pos = stacking.count() - 1; pos >= 0; --pos) { Toplevel* c = stacking.at(pos); if (c == this) // is not covered by any other window, ok to unredirect return true; if (c->geometry().intersects(geometry())) return false; } abort(); } return false; } //**************************************** // Deleted //**************************************** bool Deleted::shouldUnredirect() const { return false; } } // namespace diff --git a/main.cpp b/main.cpp index 5de2e7866..409015ac2 100644 --- a/main.cpp +++ b/main.cpp @@ -1,556 +1,556 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak 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.h" #include // kwin #include "abstract_backend.h" #include "atoms.h" #include "composite.h" #include "cursor.h" #include "input.h" #include "logind.h" #include "options.h" #include "screens.h" #include "sm.h" #include "workspace.h" #include "xcbutils.h" // KDE #include #include #include #include #include #include #include // Qt #include #include #include #include #include #include #include #include #include #include #include // system #ifdef HAVE_UNISTD_H #include #endif // HAVE_UNISTD_H #ifdef HAVE_MALLOC_H #include #endif // HAVE_MALLOC_H // xcb #include #ifndef XCB_GE_GENERIC #define XCB_GE_GENERIC 35 #endif namespace KWin { Options* options; Atoms* atoms; int screen_number = -1; bool is_multihead = false; class AlternativeWMDialog : public QDialog { public: AlternativeWMDialog() : QDialog() { QWidget* mainWidget = new QWidget(this); QVBoxLayout* layout = new QVBoxLayout(mainWidget); QString text = i18n( "KWin is unstable.\n" "It seems to have crashed several times in a row.\n" "You can select another window manager to run:"); QLabel* textLabel = new QLabel(text, mainWidget); layout->addWidget(textLabel); wmList = new QComboBox(mainWidget); wmList->setEditable(true); layout->addWidget(wmList); addWM(QStringLiteral("metacity")); addWM(QStringLiteral("openbox")); addWM(QStringLiteral("fvwm2")); addWM(QStringLiteral(KWIN_INTERNAL_NAME_X11)); QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->addWidget(mainWidget); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); buttons->button(QDialogButtonBox::Ok)->setDefault(true); connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); mainLayout->addWidget(buttons); raise(); } void addWM(const QString& wm) { // TODO: Check if WM is installed if (!QStandardPaths::findExecutable(wm).isEmpty()) wmList->addItem(wm); } QString selectedWM() const { return wmList->currentText(); } private: QComboBox* wmList; }; int Application::crashes = 0; bool Application::isX11MultiHead() { return is_multihead; } void Application::setX11MultiHead(bool multiHead) { is_multihead = multiHead; } void Application::setX11ScreenNumber(int screenNumber) { screen_number = screenNumber; } int Application::x11ScreenNumber() { return screen_number; } Application::Application(Application::OperationMode mode, int &argc, char **argv) : QApplication(argc, argv) , m_eventFilter(new XcbEventFilter()) , m_configLock(false) , m_config() , m_operationMode(mode) { qRegisterMetaType("Options::WindowOperation"); } void Application::setConfigLock(bool lock) { m_configLock = lock; } Application::OperationMode Application::operationMode() const { return m_operationMode; } void Application::setOperationMode(OperationMode mode) { m_operationMode = mode; } bool Application::shouldUseWaylandForCompositing() const { return m_operationMode == OperationModeWaylandAndX11 || m_operationMode == OperationModeXwayland; } bool Application::requiresCompositing() const { return shouldUseWaylandForCompositing(); } void Application::start() { setQuitOnLastWindowClosed(false); if (!m_config) { m_config = KSharedConfig::openConfig(); } if (!m_config->isImmutable() && m_configLock) { // TODO: This shouldn't be necessary //config->setReadOnly( true ); m_config->reparseConfiguration(); } crashChecking(); performStartup(); } Application::~Application() { delete options; destroyAtoms(); } void Application::destroyAtoms() { delete atoms; atoms = nullptr; } void Application::setupCrashHandler() { KCrash::setEmergencySaveFunction(Application::crashHandler); } void Application::crashChecking() { setupCrashHandler(); if (crashes >= 4) { // Something has gone seriously wrong AlternativeWMDialog dialog; QString cmd = QStringLiteral(KWIN_INTERNAL_NAME_X11); if (dialog.exec() == QDialog::Accepted) cmd = dialog.selectedWM(); else ::exit(1); if (cmd.length() > 500) { qCDebug(KWIN_CORE) << "Command is too long, truncating"; cmd = cmd.left(500); } qCDebug(KWIN_CORE) << "Starting" << cmd << "and exiting"; char buf[1024]; sprintf(buf, "%s &", cmd.toAscii().data()); system(buf); ::exit(1); } if (crashes >= 2) { // Disable compositing if we have had too many crashes qCDebug(KWIN_CORE) << "Too many crashes recently, disabling compositing"; KConfigGroup compgroup(KSharedConfig::openConfig(), "Compositing"); compgroup.writeEntry("Enabled", false); } // Reset crashes count if we stay up for more that 15 seconds QTimer::singleShot(15 * 1000, this, SLOT(resetCrashesCount())); } void Application::crashHandler(int signal) { crashes++; fprintf(stderr, "Application::crashHandler() called with signal %d; recent crashes: %d\n", signal, crashes); char cmd[1024]; sprintf(cmd, "%s --crashes %d &", QFile::encodeName(QCoreApplication::applicationFilePath()).constData(), crashes); sleep(1); system(cmd); } void Application::resetCrashesCount() { crashes = 0; } void Application::setCrashCount(int count) { crashes = count; } bool Application::wasCrash() { return crashes > 0; } static const char description[] = I18N_NOOP("KDE window manager"); void Application::createAboutData() { KAboutData aboutData(QStringLiteral(KWIN_NAME), // The program name used internally i18n("KWin"), // A displayable program name string QStringLiteral(KWIN_VERSION_STRING), // The program version string i18n(description), // Short description of what the app does KAboutLicense::GPL, // The license this code is released under i18n("(c) 1999-2013, The KDE Developers")); // Copyright Statement aboutData.addAuthor(i18n("Matthias Ettrich"), QString(), QStringLiteral("ettrich@kde.org")); aboutData.addAuthor(i18n("Cristian Tibirna"), QString(), QStringLiteral("tibirna@kde.org")); aboutData.addAuthor(i18n("Daniel M. Duley"), QString(), QStringLiteral("mosfet@kde.org")); aboutData.addAuthor(i18n("Luboš Luňák"), QString(), QStringLiteral("l.lunak@kde.org")); aboutData.addAuthor(i18n("Martin Gräßlin"), i18n("Maintainer"), QStringLiteral("mgraesslin@kde.org")); KAboutData::setApplicationData(aboutData); } static const QString s_lockOption = QStringLiteral("lock"); static const QString s_crashesOption = QStringLiteral("crashes"); void Application::setupCommandLine(QCommandLineParser *parser) { QCommandLineOption lockOption(s_lockOption, i18n("Disable configuration options")); QCommandLineOption crashesOption(s_crashesOption, i18n("Indicate that KWin has recently crashed n times"), QStringLiteral("n")); parser->setApplicationDescription(i18n("KDE window manager")); parser->addVersionOption(); parser->addHelpOption(); parser->addOption(lockOption); parser->addOption(crashesOption); KAboutData::applicationData().setupCommandLine(parser); } void Application::processCommandLine(QCommandLineParser *parser) { setConfigLock(parser->isSet(s_lockOption)); Application::setCrashCount(parser->value(s_crashesOption).toInt()); } void Application::setupTranslator() { QTranslator *qtTranslator = new QTranslator(qApp); qtTranslator->load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); installTranslator(qtTranslator); } void Application::setupMalloc() { #ifdef M_TRIM_THRESHOLD // Prevent fragmentation of the heap by malloc (glibc). // // The default threshold is 128*1024, which can result in a large memory usage // due to fragmentation especially if we use the raster graphicssystem. On the // otherside if the threshold is too low, free() starts to permanently ask the kernel // about shrinking the heap. #ifdef HAVE_UNISTD_H const int pagesize = sysconf(_SC_PAGESIZE); #else const int pagesize = 4*1024; #endif // HAVE_UNISTD_H mallopt(M_TRIM_THRESHOLD, 5*pagesize); #endif // M_TRIM_THRESHOLD } void Application::setupLocalizedString() { KLocalizedString::setApplicationDomain("kwin"); } void Application::notifyKSplash() { // Tell KSplash that KWin has started QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"), QStringLiteral("/KSplash"), QStringLiteral("org.kde.KSplash"), QStringLiteral("setStage")); ksplashProgressMessage.setArguments(QList() << QStringLiteral("wm")); QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); } void Application::createWorkspace() { // ensure the helper atoms are retrieved before we create the Workspace atoms->retrieveHelpers(); // we want all QQuickWindows with an alpha buffer, do here as Workspace might create QQuickWindows QQuickWindow::setDefaultAlphaBuffer(true); // This tries to detect compositing options and can use GLX. GLX problems // (X errors) shouldn't cause kwin to abort, so this is out of the // critical startup section where x errors cause kwin to abort. // create workspace. (void) new Workspace(m_originalSessionKey); emit workspaceCreated(); } void Application::createInput() { LogindIntegration::create(this); auto input = InputRedirection::create(this); input->init(); Cursor::create(this); } void Application::createScreens() { if (Screens::self()) { return; } Screens::create(this); emit screensCreated(); } void Application::createAtoms() { atoms = new Atoms; } void Application::createOptions() { options = new Options; } void Application::createCompositor() { Compositor::create(this); } void Application::setupEventFilters() { installNativeEventFilter(m_eventFilter.data()); } void Application::destroyWorkspace() { delete Workspace::self(); } void Application::destroyCompositor() { delete Compositor::self(); } void Application::updateX11Time(xcb_generic_event_t *event) { xcb_timestamp_t time = XCB_TIME_CURRENT_TIME; const uint8_t eventType = event->response_type & ~0x80; switch(eventType) { case XCB_KEY_PRESS: case XCB_KEY_RELEASE: time = reinterpret_cast(event)->time; break; case XCB_BUTTON_PRESS: case XCB_BUTTON_RELEASE: time = reinterpret_cast(event)->time; break; case XCB_MOTION_NOTIFY: time = reinterpret_cast(event)->time; break; case XCB_ENTER_NOTIFY: case XCB_LEAVE_NOTIFY: time = reinterpret_cast(event)->time; break; case XCB_FOCUS_IN: case XCB_FOCUS_OUT: case XCB_KEYMAP_NOTIFY: case XCB_EXPOSE: case XCB_GRAPHICS_EXPOSURE: case XCB_NO_EXPOSURE: case XCB_VISIBILITY_NOTIFY: case XCB_CREATE_NOTIFY: case XCB_DESTROY_NOTIFY: case XCB_UNMAP_NOTIFY: case XCB_MAP_NOTIFY: case XCB_MAP_REQUEST: case XCB_REPARENT_NOTIFY: case XCB_CONFIGURE_NOTIFY: case XCB_CONFIGURE_REQUEST: case XCB_GRAVITY_NOTIFY: case XCB_RESIZE_REQUEST: case XCB_CIRCULATE_NOTIFY: case XCB_CIRCULATE_REQUEST: // no timestamp return; case XCB_PROPERTY_NOTIFY: time = reinterpret_cast(event)->time; break; case XCB_SELECTION_CLEAR: time = reinterpret_cast(event)->time; break; case XCB_SELECTION_REQUEST: time = reinterpret_cast(event)->time; break; case XCB_SELECTION_NOTIFY: time = reinterpret_cast(event)->time; break; case XCB_COLORMAP_NOTIFY: case XCB_CLIENT_MESSAGE: case XCB_MAPPING_NOTIFY: case XCB_GE_GENERIC: // no timestamp return; default: // extension handling if (Xcb::Extensions::self()) { if (eventType == Xcb::Extensions::self()->shapeNotifyEvent()) { time = reinterpret_cast(event)->server_time; } if (eventType == Xcb::Extensions::self()->damageNotifyEvent()) { time = reinterpret_cast(event)->timestamp; } } break; } setX11Time(time); } bool XcbEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long int *result) { Q_UNUSED(result) if (eventType != "xcb_generic_event_t") { return false; } auto event = static_cast(message); kwinApp()->updateX11Time(event); if (!Workspace::self()) { // Workspace not yet created return false; } return Workspace::self()->workspaceEvent(event); } static bool s_useLibinput = false; void Application::setUseLibinput(bool use) { s_useLibinput = use; } bool Application::usesLibinput() { return s_useLibinput; } QProcessEnvironment Application::processStartupEnvironment() const { return QProcessEnvironment::systemEnvironment(); } void Application::initPlatform(const KPluginMetaData &plugin) { Q_ASSERT(!m_platform); - m_platform = qobject_cast(plugin.instantiate()); + m_platform = qobject_cast(plugin.instantiate()); if (m_platform) { m_platform->setParent(this); #if HAVE_INPUT // check whether it needs libinput const QJsonObject &metaData = plugin.rawData(); auto it = metaData.find(QStringLiteral("input")); if (it != metaData.end()) { if ((*it).isBool()) { if (!(*it).toBool()) { qCDebug(KWIN_CORE) << "Platform does not support input, enforcing libinput support"; setUseLibinput(true); } } } #endif } } } // namespace diff --git a/main.h b/main.h index e7b928ac1..83fbfb9f4 100644 --- a/main.h +++ b/main.h @@ -1,247 +1,247 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak 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 MAIN_H #define MAIN_H #include #include #include #include #include // Qt #include #include #include class KPluginMetaData; class QCommandLineParser; namespace KWin { -class AbstractBackend; +class Platform; class XcbEventFilter : public QAbstractNativeEventFilter { public: virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long int *result) override; }; class KWIN_EXPORT Application : public QApplication { Q_OBJECT Q_PROPERTY(quint32 x11Time READ x11Time WRITE setX11Time) Q_PROPERTY(quint32 x11RootWindow READ x11RootWindow CONSTANT) Q_PROPERTY(void *x11Connection READ x11Connection NOTIFY x11ConnectionChanged) Q_PROPERTY(int x11ScreenNumber READ x11ScreenNumber CONSTANT) public: /** * @brief This enum provides the various operation modes of KWin depending on the available * Windowing Systems at startup. For example whether KWin only talks to X11 or also to a Wayland * Compositor. * */ enum OperationMode { /** * @brief KWin uses only X11 for managing windows and compositing */ OperationModeX11, /** * @brief KWin uses X11 for managing windows, but renders to a Wayland compositor. * Input is received from the Wayland compositor. */ OperationModeWaylandAndX11, /** * @brief KWin uses Wayland and controls a nested Xwayland server. **/ OperationModeXwayland }; virtual ~Application(); void setConfigLock(bool lock); KSharedConfigPtr config() const { return m_config; } void setConfig(KSharedConfigPtr config) { m_config = config; } void start(); /** * @brief The operation mode used by KWin. * * @return OperationMode */ OperationMode operationMode() const; void setOperationMode(OperationMode mode); bool shouldUseWaylandForCompositing() const; bool requiresCompositing() const; void setupTranslator(); void setupCommandLine(QCommandLineParser *parser); void processCommandLine(QCommandLineParser *parser); xcb_timestamp_t x11Time() const { return m_x11Time; } void setX11Time(xcb_timestamp_t timestamp) { if (timestamp > m_x11Time) { m_x11Time = timestamp; } } void updateX11Time(xcb_generic_event_t *event); void createScreens(); static void setCrashCount(int count); static bool wasCrash(); /** * Creates the KAboutData object for the KWin instance and registers it as * KAboutData::setApplicationData. **/ static void createAboutData(); /** * @returns the X11 Screen number. If not applicable it's set to @c -1. **/ static int x11ScreenNumber(); /** * Sets the X11 screen number of this KWin instance to @p screenNumber. **/ static void setX11ScreenNumber(int screenNumber); /** * @returns whether this is a multi head setup on X11. **/ static bool isX11MultiHead(); /** * Sets whether this is a multi head setup on X11. */ static void setX11MultiHead(bool multiHead); /** * @returns the X11 root window. **/ xcb_window_t x11RootWindow() const { return m_rootWindow; } /** * @returns the X11 xcb connection **/ xcb_connection_t *x11Connection() const { return m_connection; } #ifdef KWIN_BUILD_ACTIVITIES bool usesKActivities() const { return m_useKActivities; } void setUseKActivities(bool use) { m_useKActivities = use; } #endif virtual QProcessEnvironment processStartupEnvironment() const; void initPlatform(const KPluginMetaData &plugin); - AbstractBackend *platform() const { + Platform *platform() const { return m_platform; } static void setupMalloc(); static void setupLocalizedString(); static bool usesLibinput(); static void setUseLibinput(bool use); Q_SIGNALS: void x11ConnectionChanged(); void x11ConnectionAboutToBeDestroyed(); void workspaceCreated(); void screensCreated(); void virtualTerminalCreated(); protected: Application(OperationMode mode, int &argc, char **argv); virtual void performStartup() = 0; virtual void setupCrashHandler(); void notifyKSplash(); void createInput(); void createWorkspace(); void createAtoms(); void createOptions(); void createCompositor(); void setupEventFilters(); void destroyWorkspace(); void destroyCompositor(); /** * Inheriting classes should use this method to set the X11 root window * before accessing any X11 specific code pathes. **/ void setX11RootWindow(xcb_window_t root) { m_rootWindow = root; } /** * Inheriting classes should use this method to set the xcb connection * before accessing any X11 specific code pathes. **/ void setX11Connection(xcb_connection_t *c) { m_connection = c; emit x11ConnectionChanged(); } void destroyAtoms(); static void crashHandler(int signal); protected: QString m_originalSessionKey; private Q_SLOTS: void resetCrashesCount(); private: void crashChecking(); QScopedPointer m_eventFilter; bool m_configLock; KSharedConfigPtr m_config; OperationMode m_operationMode; xcb_timestamp_t m_x11Time = XCB_TIME_CURRENT_TIME; xcb_window_t m_rootWindow = XCB_WINDOW_NONE; xcb_connection_t *m_connection = nullptr; #ifdef KWIN_BUILD_ACTIVITIES bool m_useKActivities = true; #endif - AbstractBackend *m_platform = nullptr; + Platform *m_platform = nullptr; static int crashes; }; inline static Application *kwinApp() { return static_cast(QCoreApplication::instance()); } } // namespace #endif diff --git a/main_wayland.cpp b/main_wayland.cpp index 13de72373..2f3d85130 100644 --- a/main_wayland.cpp +++ b/main_wayland.cpp @@ -1,718 +1,717 @@ /******************************************************************** 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 "workspace.h" #include // kwin #include "abstract_backend.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 #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); destroyWorkspace(); waylandServer()->dispatch(); // need to unload all effects prior to destroying X connection as they might do X calls if (effects) { static_cast(effects)->unloadAllEffects(); } 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(); createBackend(); } void ApplicationWayland::setupCrashHandler() { // this disables auto-restart of kwin_wayland // do nothing hence allowing OS to create dump and so on } void ApplicationWayland::createBackend() { - AbstractBackend *backend = kwinApp()->platform(); - connect(backend, &AbstractBackend::screensQueried, this, &ApplicationWayland::continueStartupWithScreens); - connect(backend, &AbstractBackend::initFailed, this, + 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); } ); - backend->init(); + platform()->init(); } void ApplicationWayland::continueStartupWithScreens() { - disconnect(kwinApp()->platform(), &AbstractBackend::screensQueried, this, &ApplicationWayland::continueStartupWithScreens); + disconnect(kwinApp()->platform(), &Platform::screensQueried, this, &ApplicationWayland::continueStartupWithScreens); createScreens(); waylandServer()->initOutputs(); if (!m_startXWayland) { continueStartupWithX(); return; } createCompositor(); 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() { 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 } } // 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("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/pointer_input.cpp b/pointer_input.cpp index c9acb9ad9..8fe569f42 100644 --- a/pointer_input.cpp +++ b/pointer_input.cpp @@ -1,902 +1,902 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013, 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 "pointer_input.h" #include "abstract_backend.h" #include "effects.h" #include "screens.h" #include "shell_client.h" #include "wayland_cursor_theme.h" #include "wayland_server.h" #include "workspace.h" #include "decorations/decoratedclient.h" // KDecoration #include // KWayland #include #include #include #include #include #include #include // screenlocker #include #include #include // Wayland #include #include namespace KWin { static Qt::MouseButton buttonToQtMouseButton(uint32_t button) { switch (button) { case BTN_LEFT: return Qt::LeftButton; case BTN_MIDDLE: return Qt::MiddleButton; case BTN_RIGHT: return Qt::RightButton; case BTN_SIDE: // in QtWayland mapped like that return Qt::ExtraButton1; case BTN_EXTRA: // in QtWayland mapped like that return Qt::ExtraButton2; case BTN_BACK: return Qt::BackButton; case BTN_FORWARD: return Qt::ForwardButton; case BTN_TASK: return Qt::TaskButton; // mapped like that in QtWayland case 0x118: return Qt::ExtraButton6; case 0x119: return Qt::ExtraButton7; case 0x11a: return Qt::ExtraButton8; case 0x11b: return Qt::ExtraButton9; case 0x11c: return Qt::ExtraButton10; case 0x11d: return Qt::ExtraButton11; case 0x11e: return Qt::ExtraButton12; case 0x11f: return Qt::ExtraButton13; } // all other values get mapped to ExtraButton24 // this is actually incorrect but doesn't matter in our usage // KWin internally doesn't use these high extra buttons anyway // it's only needed for recognizing whether buttons are pressed // if multiple buttons are mapped to the value the evaluation whether // buttons are pressed is correct and that's all we care about. return Qt::ExtraButton24; } static bool screenContainsPos(const QPointF &pos) { for (int i = 0; i < screens()->count(); ++i) { if (screens()->geometry(i).contains(pos.toPoint())) { return true; } } return false; } PointerInputRedirection::PointerInputRedirection(InputRedirection* parent) : QObject(parent) , m_input(parent) , m_cursor(nullptr) , m_supportsWarping(Application::usesLibinput()) { } PointerInputRedirection::~PointerInputRedirection() = default; void PointerInputRedirection::init() { Q_ASSERT(!m_inited); m_cursor = new CursorImage(this); m_inited = true; - connect(m_cursor, &CursorImage::changed, kwinApp()->platform(), &AbstractBackend::cursorChanged); + connect(m_cursor, &CursorImage::changed, kwinApp()->platform(), &Platform::cursorChanged); emit m_cursor->changed(); connect(workspace(), &Workspace::stackingOrderChanged, this, &PointerInputRedirection::update); connect(screens(), &Screens::changed, this, &PointerInputRedirection::updateAfterScreenChange); connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &PointerInputRedirection::update); connect(workspace(), &QObject::destroyed, this, [this] { m_inited = false; }); connect(waylandServer(), &QObject::destroyed, this, [this] { m_inited = false; }); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragEnded, this, [this] { // need to force a focused pointer change waylandServer()->seat()->setFocusedPointerSurface(nullptr); m_window.clear(); update(); } ); // warp the cursor to center of screen warp(screens()->geometry().center()); updateAfterScreenChange(); } void PointerInputRedirection::processMotion(const QPointF &pos, uint32_t time) { if (!m_inited) { return; } updatePosition(pos); QMouseEvent event(QEvent::MouseMove, m_pos.toPoint(), m_pos.toPoint(), Qt::NoButton, m_qtButtons, m_input->keyboardModifiers()); event.setTimestamp(time); const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->pointerEvent(&event, 0)) { return; } } } void PointerInputRedirection::processButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time) { if (!m_inited) { return; } updateButton(button, state); QEvent::Type type; switch (state) { case InputRedirection::PointerButtonReleased: type = QEvent::MouseButtonRelease; break; case InputRedirection::PointerButtonPressed: type = QEvent::MouseButtonPress; break; default: Q_UNREACHABLE(); return; } QMouseEvent event(type, m_pos.toPoint(), m_pos.toPoint(), buttonToQtMouseButton(button), m_qtButtons, m_input->keyboardModifiers()); event.setTimestamp(time); const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->pointerEvent(&event, button)) { return; } } } void PointerInputRedirection::processAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time) { if (!m_inited) { return; } if (delta == 0) { return; } emit m_input->pointerAxisChanged(axis, delta); QWheelEvent wheelEvent(m_pos, m_pos, QPoint(), (axis == InputRedirection::PointerAxisHorizontal) ? QPoint(delta, 0) : QPoint(0, delta), delta, (axis == InputRedirection::PointerAxisHorizontal) ? Qt::Horizontal : Qt::Vertical, m_qtButtons, m_input->keyboardModifiers()); wheelEvent.setTimestamp(time); const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->wheelEvent(&wheelEvent)) { return; } } } void PointerInputRedirection::update() { if (!m_inited) { return; } if (waylandServer()->seat()->isDragPointer()) { // ignore during drag and drop return; } // TODO: handle pointer grab aka popups Toplevel *t = m_input->findToplevel(m_pos.toPoint()); const auto oldDeco = m_decoration; updateInternalWindow(); if (!m_internalWindow) { updateDecoration(t); } else { // TODO: send hover leave to decoration if (m_decoration) { m_decoration->client()->leaveEvent(); } m_decoration.clear(); } if (m_decoration || m_internalWindow) { t = nullptr; } if (m_decoration != oldDeco) { emit decorationChanged(); } auto oldWindow = m_window; if (!oldWindow.isNull() && t == m_window.data()) { return; } auto seat = waylandServer()->seat(); // disconnect old surface if (oldWindow) { if (AbstractClient *c = qobject_cast(oldWindow.data())) { c->leaveEvent(); } disconnect(m_windowGeometryConnection); m_windowGeometryConnection = QMetaObject::Connection(); } if (AbstractClient *c = qobject_cast(t)) { // only send enter if it wasn't on deco for the same client before if (m_decoration.isNull() || m_decoration->client() != c) { c->enterEvent(m_pos.toPoint()); workspace()->updateFocusMousePosition(m_pos.toPoint()); } } if (t && t->surface()) { m_window = QPointer(t); seat->setFocusedPointerSurface(t->surface(), t->inputTransformation()); m_windowGeometryConnection = connect(t, &Toplevel::geometryChanged, this, [this] { if (m_window.isNull()) { return; } // TODO: can we check on the client instead? if (workspace()->getMovingClient()) { // don't update while moving return; } auto seat = waylandServer()->seat(); if (m_window.data()->surface() != seat->focusedPointerSurface()) { return; } seat->setFocusedPointerSurfaceTransformation(m_window.data()->inputTransformation()); } ); } else { m_window.clear(); seat->setFocusedPointerSurface(nullptr); t = nullptr; } } void PointerInputRedirection::updateInternalWindow() { const auto oldInternalWindow = m_internalWindow; bool found = false; // TODO: screen locked check without going through wayland server bool needsReset = waylandServer()->isScreenLocked(); const auto &internalClients = waylandServer()->internalClients(); const bool change = m_internalWindow.isNull() || !(m_internalWindow->flags().testFlag(Qt::Popup) && m_internalWindow->isVisible()); if (!internalClients.isEmpty() && change) { auto it = internalClients.end(); do { it--; if (QWindow *w = (*it)->internalWindow()) { if (!w->isVisible()) { continue; } if (w->geometry().contains(m_pos.toPoint())) { m_internalWindow = QPointer(w); found = true; break; } } } while (it != internalClients.begin()); if (!found) { needsReset = true; } } if (needsReset) { m_internalWindow.clear(); } if (oldInternalWindow != m_internalWindow) { // changed if (oldInternalWindow) { disconnect(m_internalWindowConnection); m_internalWindowConnection = QMetaObject::Connection(); QEvent event(QEvent::Leave); QCoreApplication::sendEvent(oldInternalWindow.data(), &event); } if (m_internalWindow) { m_internalWindowConnection = connect(m_internalWindow.data(), &QWindow::visibleChanged, this, [this] (bool visible) { if (!visible) { update(); } }); QEnterEvent event(m_pos - m_internalWindow->position(), m_pos - m_internalWindow->position(), m_pos); QCoreApplication::sendEvent(m_internalWindow.data(), &event); return; } } } void PointerInputRedirection::updateDecoration(Toplevel *t) { const auto oldDeco = m_decoration; bool needsReset = waylandServer()->isScreenLocked(); if (AbstractClient *c = dynamic_cast(t)) { // check whether it's on a Decoration if (c->decoratedClient()) { const QRect clientRect = QRect(c->clientPos(), c->clientSize()).translated(c->pos()); if (!clientRect.contains(m_pos.toPoint())) { m_decoration = c->decoratedClient(); } else { needsReset = true; } } else { needsReset = true; } } else { needsReset = true; } if (needsReset) { m_decoration.clear(); } bool leftSend = false; auto oldWindow = qobject_cast(m_window.data()); if (oldWindow && (m_decoration && m_decoration->client() != oldWindow)) { leftSend = true; oldWindow->leaveEvent(); } if (oldDeco && oldDeco != m_decoration) { if (oldDeco->client() != t && !leftSend) { leftSend = true; oldDeco->client()->leaveEvent(); } // send leave QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF()); QCoreApplication::instance()->sendEvent(oldDeco->decoration(), &event); } if (m_decoration) { if (m_decoration->client() != oldWindow) { m_decoration->client()->enterEvent(m_pos.toPoint()); workspace()->updateFocusMousePosition(m_pos.toPoint()); } const QPointF p = m_pos - t->pos(); QHoverEvent event(QEvent::HoverMove, p, p); QCoreApplication::instance()->sendEvent(m_decoration->decoration(), &event); m_decoration->client()->processDecorationMove(p.toPoint(), m_pos.toPoint()); } } void PointerInputRedirection::updatePosition(const QPointF &pos) { // verify that at least one screen contains the pointer position QPointF p = pos; if (!screenContainsPos(p)) { // allow either x or y to pass p = QPointF(m_pos.x(), pos.y()); if (!screenContainsPos(p)) { p = QPointF(pos.x(), m_pos.y()); if (!screenContainsPos(p)) { return; } } } m_pos = p; emit m_input->globalPointerChanged(m_pos); } void PointerInputRedirection::updateButton(uint32_t button, InputRedirection::PointerButtonState state) { m_buttons[button] = state; // update Qt buttons m_qtButtons = Qt::NoButton; for (auto it = m_buttons.constBegin(); it != m_buttons.constEnd(); ++it) { if (it.value() == InputRedirection::PointerButtonReleased) { continue; } m_qtButtons |= buttonToQtMouseButton(it.key()); } emit m_input->pointerButtonStateChanged(button, state); } void PointerInputRedirection::warp(const QPointF &pos) { if (supportsWarping()) { kwinApp()->platform()->warpPointer(pos); processMotion(pos, waylandServer()->seat()->timestamp()); } } bool PointerInputRedirection::supportsWarping() const { if (!m_inited) { return false; } if (m_supportsWarping) { return true; } if (kwinApp()->platform()->supportsPointerWarping()) { return true; } return false; } void PointerInputRedirection::updateAfterScreenChange() { if (!m_inited) { return; } if (screenContainsPos(m_pos)) { // pointer still on a screen return; } // pointer no longer on a screen, reposition to closes screen const QPointF pos = screens()->geometry(screens()->number(m_pos.toPoint())).center(); // TODO: better way to get timestamps processMotion(pos, waylandServer()->seat()->timestamp()); } QImage PointerInputRedirection::cursorImage() const { if (!m_inited) { return QImage(); } return m_cursor->image(); } QPoint PointerInputRedirection::cursorHotSpot() const { if (!m_inited) { return QPoint(); } return m_cursor->hotSpot(); } void PointerInputRedirection::markCursorAsRendered() { if (!m_inited) { return; } m_cursor->markAsRendered(); } void PointerInputRedirection::setEffectsOverrideCursor(Qt::CursorShape shape) { if (!m_inited) { return; } // current pointer focus window should get a leave event update(); m_cursor->setEffectsOverrideCursor(shape); } void PointerInputRedirection::removeEffectsOverrideCursor() { if (!m_inited) { return; } // cursor position might have changed while there was an effect in place update(); m_cursor->removeEffectsOverrideCursor(); } CursorImage::CursorImage(PointerInputRedirection *parent) : QObject(parent) , m_pointer(parent) { connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::focusedPointerChanged, this, &CursorImage::update); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragStarted, this, &CursorImage::updateDrag); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragEnded, this, [this] { disconnect(m_drag.connection); reevaluteSource(); } ); connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &CursorImage::reevaluteSource); connect(m_pointer, &PointerInputRedirection::decorationChanged, this, &CursorImage::updateDecoration); // connect the move resize of all window auto setupMoveResizeConnection = [this] (AbstractClient *c) { connect(c, &AbstractClient::moveResizedChanged, this, &CursorImage::updateMoveResize); }; const auto clients = workspace()->allClientList(); std::for_each(clients.begin(), clients.end(), setupMoveResizeConnection); connect(workspace(), &Workspace::clientAdded, this, setupMoveResizeConnection); connect(waylandServer(), &WaylandServer::shellClientAdded, this, setupMoveResizeConnection); loadThemeCursor(Qt::ArrowCursor, &m_fallbackCursor); if (m_cursorTheme) { connect(m_cursorTheme, &WaylandCursorTheme::themeChanged, this, [this] { m_cursors.clear(); loadThemeCursor(Qt::ArrowCursor, &m_fallbackCursor); updateDecorationCursor(); updateMoveResize(); // TODO: update effects } ); } m_surfaceRenderedTimer.start(); } CursorImage::~CursorImage() = default; void CursorImage::markAsRendered() { if (m_currentSource == CursorSource::DragAndDrop) { // always sending a frame rendered to the drag icon surface to not freeze QtWayland (see https://bugreports.qt.io/browse/QTBUG-51599 ) if (auto ddi = waylandServer()->seat()->dragSource()) { if (auto s = ddi->icon()) { s->frameRendered(m_surfaceRenderedTimer.elapsed()); } } auto p = waylandServer()->seat()->dragPointer(); if (!p) { return; } auto c = p->cursor(); if (!c) { return; } auto cursorSurface = c->surface(); if (cursorSurface.isNull()) { return; } cursorSurface->frameRendered(m_surfaceRenderedTimer.elapsed()); return; } if (m_currentSource != CursorSource::LockScreen && m_currentSource != CursorSource::PointerSurface) { return; } auto p = waylandServer()->seat()->focusedPointer(); if (!p) { return; } auto c = p->cursor(); if (!c) { return; } auto cursorSurface = c->surface(); if (cursorSurface.isNull()) { return; } cursorSurface->frameRendered(m_surfaceRenderedTimer.elapsed()); } void CursorImage::update() { using namespace KWayland::Server; disconnect(m_serverCursor.connection); auto p = waylandServer()->seat()->focusedPointer(); if (p) { m_serverCursor.connection = connect(p, &PointerInterface::cursorChanged, this, &CursorImage::updateServerCursor); } else { m_serverCursor.connection = QMetaObject::Connection(); } updateServerCursor(); } void CursorImage::updateDecoration() { disconnect(m_decorationConnection); auto deco = m_pointer->decoration(); AbstractClient *c = deco.isNull() ? nullptr : deco->client(); if (c) { m_decorationConnection = connect(c, &AbstractClient::moveResizeCursorChanged, this, &CursorImage::updateDecorationCursor); } else { m_decorationConnection = QMetaObject::Connection(); } updateDecorationCursor(); } void CursorImage::updateDecorationCursor() { m_decorationCursor.image = QImage(); m_decorationCursor.hotSpot = QPoint(); auto deco = m_pointer->decoration(); if (AbstractClient *c = deco.isNull() ? nullptr : deco->client()) { loadThemeCursor(c->cursor(), &m_decorationCursor); if (m_currentSource == CursorSource::Decoration) { emit changed(); } } reevaluteSource(); } void CursorImage::updateMoveResize() { m_moveResizeCursor.image = QImage(); m_moveResizeCursor.hotSpot = QPoint(); if (AbstractClient *c = workspace()->getMovingClient()) { loadThemeCursor(c->isMove() ? Qt::SizeAllCursor : Qt::SizeBDiagCursor, &m_moveResizeCursor); if (m_currentSource == CursorSource::MoveResize) { emit changed(); } } reevaluteSource(); } void CursorImage::updateServerCursor() { m_serverCursor.image = QImage(); m_serverCursor.hotSpot = QPoint(); reevaluteSource(); const bool needsEmit = m_currentSource == CursorSource::LockScreen || m_currentSource == CursorSource::PointerSurface; auto p = waylandServer()->seat()->focusedPointer(); if (!p) { if (needsEmit) { emit changed(); } return; } auto c = p->cursor(); if (!c) { if (needsEmit) { emit changed(); } return; } auto cursorSurface = c->surface(); if (cursorSurface.isNull()) { if (needsEmit) { emit changed(); } return; } auto buffer = cursorSurface.data()->buffer(); if (!buffer) { if (needsEmit) { emit changed(); } return; } m_serverCursor.hotSpot = c->hotspot(); m_serverCursor.image = buffer->data().copy(); if (needsEmit) { emit changed(); } } void CursorImage::loadTheme() { if (m_cursorTheme) { return; } // check whether we can create it if (waylandServer()->internalShmPool()) { m_cursorTheme = new WaylandCursorTheme(waylandServer()->internalShmPool(), this); connect(waylandServer(), &WaylandServer::terminatingInternalClientConnection, this, [this] { delete m_cursorTheme; m_cursorTheme = nullptr; } ); } } void CursorImage::setEffectsOverrideCursor(Qt::CursorShape shape) { loadThemeCursor(shape, &m_effectsCursor); if (m_currentSource == CursorSource::EffectsOverride) { emit changed(); } reevaluteSource(); } void CursorImage::removeEffectsOverrideCursor() { reevaluteSource(); } void CursorImage::updateDrag() { using namespace KWayland::Server; disconnect(m_drag.connection); m_drag.cursor.image = QImage(); m_drag.cursor.hotSpot = QPoint(); reevaluteSource(); if (auto p = waylandServer()->seat()->dragPointer()) { m_drag.connection = connect(p, &PointerInterface::cursorChanged, this, &CursorImage::updateDragCursor); } else { m_drag.connection = QMetaObject::Connection(); } updateDragCursor(); } void CursorImage::updateDragCursor() { m_drag.cursor.image = QImage(); m_drag.cursor.hotSpot = QPoint(); const bool needsEmit = m_currentSource == CursorSource::DragAndDrop; QImage additionalIcon; if (auto ddi = waylandServer()->seat()->dragSource()) { if (auto dragIcon = ddi->icon()) { if (auto buffer = dragIcon->buffer()) { additionalIcon = buffer->data().copy(); } } } auto p = waylandServer()->seat()->dragPointer(); if (!p) { if (needsEmit) { emit changed(); } return; } auto c = p->cursor(); if (!c) { if (needsEmit) { emit changed(); } return; } auto cursorSurface = c->surface(); if (cursorSurface.isNull()) { if (needsEmit) { emit changed(); } return; } auto buffer = cursorSurface.data()->buffer(); if (!buffer) { if (needsEmit) { emit changed(); } return; } m_drag.cursor.hotSpot = c->hotspot(); m_drag.cursor.image = buffer->data().copy(); if (needsEmit) { emit changed(); } // TODO: add the cursor image } void CursorImage::loadThemeCursor(Qt::CursorShape shape, Image *image) { loadTheme(); if (!m_cursorTheme) { return; } auto it = m_cursors.constFind(shape); if (it == m_cursors.constEnd()) { image->image = QImage(); image->hotSpot = QPoint(); wl_cursor_image *cursor = m_cursorTheme->get(shape); if (!cursor) { return; } wl_buffer *b = wl_cursor_image_get_buffer(cursor); if (!b) { return; } waylandServer()->internalClientConection()->flush(); waylandServer()->dispatch(); auto buffer = KWayland::Server::BufferInterface::get(waylandServer()->internalConnection()->getResource(KWayland::Client::Buffer::getId(b))); if (!buffer) { return; } it = decltype(it)(m_cursors.insert(shape, {buffer->data().copy(), QPoint(cursor->hotspot_x, cursor->hotspot_y)})); } image->hotSpot = it.value().hotSpot; image->image = it.value().image; } void CursorImage::reevaluteSource() { if (waylandServer()->seat()->isDragPointer()) { // TODO: touch drag? setSource(CursorSource::DragAndDrop); return; } if (waylandServer()->isScreenLocked()) { setSource(CursorSource::LockScreen); return; } if (effects && static_cast(effects)->isMouseInterception()) { setSource(CursorSource::EffectsOverride); return; } if (workspace() && workspace()->getMovingClient()) { setSource(CursorSource::MoveResize); return; } if (!m_pointer->decoration().isNull()) { setSource(CursorSource::Decoration); return; } if (!m_pointer->window().isNull()) { setSource(CursorSource::PointerSurface); return; } setSource(CursorSource::Fallback); } void CursorImage::setSource(CursorSource source) { if (m_currentSource == source) { return; } m_currentSource = source; emit changed(); } QImage CursorImage::image() const { switch (m_currentSource) { case CursorSource::EffectsOverride: return m_effectsCursor.image; case CursorSource::MoveResize: return m_moveResizeCursor.image; case CursorSource::LockScreen: case CursorSource::PointerSurface: // lockscreen also uses server cursor image return m_serverCursor.image; case CursorSource::Decoration: return m_decorationCursor.image; case CursorSource::DragAndDrop: return m_drag.cursor.image; case CursorSource::Fallback: return m_fallbackCursor.image; default: Q_UNREACHABLE(); } } QPoint CursorImage::hotSpot() const { switch (m_currentSource) { case CursorSource::EffectsOverride: return m_effectsCursor.hotSpot; case CursorSource::MoveResize: return m_moveResizeCursor.hotSpot; case CursorSource::LockScreen: case CursorSource::PointerSurface: // lockscreen also uses server cursor image return m_serverCursor.hotSpot; case CursorSource::Decoration: return m_decorationCursor.hotSpot; case CursorSource::DragAndDrop: return m_drag.cursor.hotSpot; case CursorSource::Fallback: return m_fallbackCursor.hotSpot; default: Q_UNREACHABLE(); } } } diff --git a/screens.cpp b/screens.cpp index b4d6a46bf..77243761e 100644 --- a/screens.cpp +++ b/screens.cpp @@ -1,249 +1,249 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "screens.h" #include #include #include "cursor.h" #include "utils.h" #include "settings.h" #include #include #include "screens_xrandr.h" #include "abstract_backend.h" #include "wayland_server.h" #ifdef KWIN_UNIT_TEST #include #endif namespace KWin { Screens *Screens::s_self = nullptr; Screens *Screens::create(QObject *parent) { Q_ASSERT(!s_self); #ifdef KWIN_UNIT_TEST s_self = new MockScreens(parent); #else if (kwinApp()->shouldUseWaylandForCompositing()) { s_self = kwinApp()->platform()->createScreens(parent); } if (kwinApp()->operationMode() == Application::OperationModeX11) { s_self = new XRandRScreens(parent); } #endif Q_ASSERT(s_self); s_self->init(); return s_self; } Screens::Screens(QObject *parent) : QObject(parent) , m_count(0) , m_current(0) , m_currentFollowsMouse(false) , m_changedTimer(new QTimer(this)) { } Screens::~Screens() { s_self = NULL; } void Screens::init() { m_changedTimer->setSingleShot(true); m_changedTimer->setInterval(100); connect(m_changedTimer, SIGNAL(timeout()), SLOT(updateCount())); connect(m_changedTimer, SIGNAL(timeout()), SIGNAL(changed())); connect(this, &Screens::countChanged, this, &Screens::changed, Qt::QueuedConnection); connect(this, &Screens::changed, this, &Screens::updateSize); connect(this, &Screens::sizeChanged, this, &Screens::geometryChanged); Settings settings; settings.setDefaults(); m_currentFollowsMouse = settings.activeMouseScreen(); } QString Screens::name(int screen) const { Q_UNUSED(screen) qCWarning(KWIN_CORE, "%s::name(int screen) is a stub, please reimplement it!", metaObject()->className()); return QLatin1String("DUMMY"); } float Screens::refreshRate(int screen) const { Q_UNUSED(screen) qCWarning(KWIN_CORE, "%s::refreshRate(int screen) is a stub, please reimplement it!", metaObject()->className()); return 60.0f; } void Screens::reconfigure() { if (!m_config) { return; } Settings settings(m_config); settings.read(); setCurrentFollowsMouse(settings.activeMouseScreen()); } void Screens::updateSize() { QRect bounding; for (int i = 0; i < count(); ++i) { bounding = bounding.united(geometry(i)); } if (m_boundingSize != bounding.size()) { m_boundingSize = bounding.size(); emit sizeChanged(); } } void Screens::setCount(int count) { if (m_count == count) { return; } const int previous = m_count; m_count = count; emit countChanged(previous, count); } void Screens::setCurrent(int current) { if (m_current == current) { return; } m_current = current; emit currentChanged(); } void Screens::setCurrent(const QPoint &pos) { setCurrent(number(pos)); } void Screens::setCurrent(const AbstractClient *c) { if (!c->isActive()) { return; } if (!c->isOnScreen(m_current)) { setCurrent(c->screen()); } } void Screens::setCurrentFollowsMouse(bool follows) { if (m_currentFollowsMouse == follows) { return; } m_currentFollowsMouse = follows; } int Screens::current() const { if (m_currentFollowsMouse) { return number(Cursor::pos()); } AbstractClient *client = Workspace::self()->activeClient(); if (client && !client->isOnScreen(m_current)) { return client->screen(); } return m_current; } int Screens::intersecting(const QRect &r) const { int cnt = 0; for (int i = 0; i < count(); ++i) { if (geometry(i).intersects(r)) { ++cnt; } } return cnt; } -BasicScreens::BasicScreens(AbstractBackend *backend, QObject *parent) +BasicScreens::BasicScreens(Platform *backend, QObject *parent) : Screens(parent) , m_backend(backend) { } BasicScreens::~BasicScreens() = default; void BasicScreens::init() { KWin::Screens::init(); #ifndef KWIN_UNIT_TEST - connect(m_backend, &AbstractBackend::screenSizeChanged, + connect(m_backend, &Platform::screenSizeChanged, this, &BasicScreens::startChangedTimer); #endif updateCount(); emit changed(); } QRect BasicScreens::geometry(int screen) const { if (screen < m_geometries.count()) { return m_geometries.at(screen); } return QRect(); } QSize BasicScreens::size(int screen) const { if (screen < m_geometries.count()) { return m_geometries.at(screen).size(); } return QSize(); } void BasicScreens::updateCount() { m_geometries = m_backend->screenGeometries(); setCount(m_geometries.count()); } int BasicScreens::number(const QPoint &pos) const { int bestScreen = 0; int minDistance = INT_MAX; for (int i = 0; i < m_geometries.count(); ++i) { const QRect &geo = m_geometries.at(i); if (geo.contains(pos)) { return i; } int distance = QPoint(geo.topLeft() - pos).manhattanLength(); distance = qMin(distance, QPoint(geo.topRight() - pos).manhattanLength()); distance = qMin(distance, QPoint(geo.bottomRight() - pos).manhattanLength()); distance = qMin(distance, QPoint(geo.bottomLeft() - pos).manhattanLength()); if (distance < minDistance) { minDistance = distance; bestScreen = i; } } return bestScreen; } } // namespace diff --git a/screens.h b/screens.h index 859ed3cbe..1fb983d9c 100644 --- a/screens.h +++ b/screens.h @@ -1,218 +1,218 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_SCREENS_H #define KWIN_SCREENS_H // KWin includes #include // KDE includes #include #include // Qt includes #include #include #include #include namespace KWin { class AbstractClient; -class AbstractBackend; +class Platform; class KWIN_EXPORT Screens : public QObject { Q_OBJECT Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged) Q_PROPERTY(int current READ current WRITE setCurrent NOTIFY currentChanged) Q_PROPERTY(bool currentFollowsMouse READ isCurrentFollowsMouse WRITE setCurrentFollowsMouse) public: virtual ~Screens(); /** * @internal **/ void setConfig(KSharedConfig::Ptr config); int count() const; int current() const; void setCurrent(int current); /** * Called e.g. when a user clicks on a window, set current screen to be the screen * where the click occurred */ void setCurrent(const QPoint &pos); /** * Check whether a client moved completely out of what's considered the current screen, * if yes, set a new active screen. */ void setCurrent(const AbstractClient *c); bool isCurrentFollowsMouse() const; void setCurrentFollowsMouse(bool follows); virtual QRect geometry(int screen) const = 0; /** * The bounding geometry of all screens combined. Overlapping areas * are not counted multiple times. * @see geometryChanged() **/ QRect geometry() const; /** * The output name of the screen (usually eg. LVDS-1, VGA-0 or DVI-I-1 etc.) */ virtual QString name(int screen) const; /** * @returns current refreshrate of the @p screen. **/ virtual float refreshRate(int screen) const; /** * @returns size of the @p screen. * * To get the size of all screens combined use size(). * @see size() **/ virtual QSize size(int screen) const = 0; /** * The bounding size of all screens combined. Overlapping areas * are not counted multiple times. * * @see geometry() * @see sizeChanged() **/ QSize size() const; virtual int number(const QPoint &pos) const = 0; inline bool isChanging() { return m_changedTimer->isActive(); } int intersecting(const QRect &r) const; public Q_SLOTS: void reconfigure(); Q_SIGNALS: void countChanged(int previousCount, int newCount); /** * Emitted whenever the screens are changed either count or geometry. **/ void changed(); void currentChanged(); /** * Emitted when the geometry of all screens combined changes. * Not emitted when the geometry of an individual screen changes. * @see geometry() **/ void geometryChanged(); /** * Emitted when the size of all screens combined changes. * Not emitted when the size of an individual screen changes. * @see size() **/ void sizeChanged(); protected Q_SLOTS: void setCount(int count); void startChangedTimer(); virtual void updateCount() = 0; protected: /** * Called once the singleton instance has been created. * Any initialization code should go into this method. Overriding classes have to call * the base implementation first. **/ virtual void init(); private Q_SLOTS: void updateSize(); private: int m_count; int m_current; bool m_currentFollowsMouse; QTimer *m_changedTimer; KSharedConfig::Ptr m_config; QSize m_boundingSize; KWIN_SINGLETON(Screens) }; /** * @brief A base implementation for backends with just a (nested) window **/ class KWIN_EXPORT BasicScreens : public Screens { Q_OBJECT public: - BasicScreens(AbstractBackend *backend, QObject *parent = nullptr); + BasicScreens(Platform *backend, QObject *parent = nullptr); virtual ~BasicScreens(); void init() override; QRect geometry(int screen) const override; int number(const QPoint &pos) const override; QSize size(int screen) const override; void updateCount() override; private: - AbstractBackend *m_backend; + Platform *m_backend; QVector m_geometries; }; inline void Screens::setConfig(KSharedConfig::Ptr config) { m_config = config; } inline int Screens::count() const { return m_count; } inline bool Screens::isCurrentFollowsMouse() const { return m_currentFollowsMouse; } inline void Screens::startChangedTimer() { m_changedTimer->start(); } inline QSize Screens::size() const { return m_boundingSize; } inline QRect Screens::geometry() const { return QRect(QPoint(0,0), size()); } inline Screens *screens() { return Screens::self(); } } #endif // KWIN_SCREENS_H