kwin/main.cpp
Vlad Zahorodnii b8a70e62d5 Introduce RenderLoop
At the moment, our frame scheduling infrastructure is still heavily
based on Xinerama-style rendering. Specifically, we assume that painting
is driven by a single timer, etc.

This change introduces a new type - RenderLoop. Its main purpose is to
drive compositing on a specific output, or in case of X11, on the
overlay window.

With RenderLoop, compositing is synchronized to vblank events. It
exposes the last and the next estimated presentation timestamp. The
expected presentation timestamp can be used by effects to ensure that
animations are synchronized with the upcoming vblank event.

On Wayland, every outputs has its own render loop. On X11, per screen
rendering is not possible, therefore the platform exposes the render
loop for the overlay window. Ideally, the Scene has to expose the
RenderLoop, but as the first step towards better compositing scheduling
it's good as is for the time being.

The RenderLoop tries to minimize the latency by delaying compositing as
close as possible to the next vblank event. One tricky thing about it is
that if compositing is too close to the next vblank event, animations
may become a little bit choppy. However, increasing the latency reduces
the choppiness.

Given that, there is no any "silver bullet" solution for the choppiness
issue, a new option has been added in the Compositing KCM to specify the
amount of latency. By default, it's "Medium," but if a user is not
satisfied with the upstream default, they can tweak it.
2021-01-06 16:59:29 +00:00

489 lines
13 KiB
C++

/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich <ettrich@kde.org>
SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "main.h"
// kwin
#include "platform.h"
#include "atoms.h"
#ifdef KWIN_BUILD_CMS
#include "colormanager.h"
#endif
#include "composite.h"
#include "cursor.h"
#include "input.h"
#include "logind.h"
#include "options.h"
#include "pluginmanager.h"
#include "screens.h"
#include "screenlockerwatcher.h"
#include "sm.h"
#include "workspace.h"
#include "xcbutils.h"
#include <kwineffects.h>
// KDE
#include <KAboutData>
#include <KLocalizedString>
#include <KPluginMetaData>
#include <KWaylandServer/surface_interface.h>
// Qt
#include <qplatformdefs.h>
#include <QCommandLineParser>
#include <QQuickWindow>
#include <QStandardPaths>
#include <QTranslator>
#include <QLibraryInfo>
// system
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif // HAVE_UNISTD_H
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif // HAVE_MALLOC_H
// xcb
#include <xcb/damage.h>
#ifndef XCB_GE_GENERIC
#define XCB_GE_GENERIC 35
#endif
Q_DECLARE_METATYPE(KSharedConfigPtr)
namespace KWin
{
Options* options;
Atoms* atoms;
int screen_number = -1;
bool is_multihead = false;
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_kxkbConfig()
, m_operationMode(mode)
{
qRegisterMetaType<Options::WindowOperation>("Options::WindowOperation");
qRegisterMetaType<KWin::EffectWindow*>();
qRegisterMetaType<KWaylandServer::SurfaceInterface *>("KWaylandServer::SurfaceInterface *");
qRegisterMetaType<KSharedConfigPtr>();
qRegisterMetaType<std::chrono::nanoseconds>();
}
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 == OperationModeWaylandOnly || m_operationMode == OperationModeXwayland;
}
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();
}
if (!m_kxkbConfig) {
m_kxkbConfig = KSharedConfig::openConfig(QStringLiteral("kxkbrc"), KConfig::NoGlobals);
}
performStartup();
}
Application::~Application()
{
delete options;
destroyAtoms();
destroyPlatform();
}
void Application::notifyStarted()
{
emit started();
}
void Application::destroyAtoms()
{
delete atoms;
atoms = nullptr;
}
void Application::destroyPlatform()
{
delete m_platform;
m_platform = nullptr;
}
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-2019, 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 Flöser"), QString(), QStringLiteral("mgraesslin@kde.org"));
aboutData.addAuthor(i18n("David Edmundson"), QStringLiteral("Maintainer"), QStringLiteral("davidedmundson@kde.org"));
aboutData.addAuthor(i18n("Roman Gilg"), QStringLiteral("Maintainer"), QStringLiteral("subdiff@gmail.com"));
aboutData.addAuthor(i18n("Vlad Zahorodnii"), QStringLiteral("Maintainer"), QStringLiteral("vlad.zahorodnii@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->addOption(lockOption);
parser->addOption(crashesOption);
KAboutData::applicationData().setupCommandLine(parser);
}
void Application::processCommandLine(QCommandLineParser *parser)
{
KAboutData aboutData = KAboutData::applicationData();
aboutData.processCommandLine(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::createWorkspace()
{
// 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();
emit workspaceCreated();
}
void Application::createSession()
{
LogindIntegration::create(this);
}
void Application::createInput()
{
ScreenLockerWatcher::create(this);
auto input = InputRedirection::create(this);
input->init();
m_platform->createPlatformCursor(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::createPlugins()
{
PluginManager::create(this);
}
void Application::createColorManager()
{
#ifdef KWIN_BUILD_CMS
ColorManager::create(this);
#endif
}
void Application::installNativeX11EventFilter()
{
installNativeEventFilter(m_eventFilter.data());
}
void Application::removeNativeX11EventFilter()
{
removeNativeEventFilter(m_eventFilter.data());
}
void Application::destroyWorkspace()
{
delete Workspace::self();
}
void Application::destroyCompositor()
{
delete Compositor::self();
}
void Application::destroyPlugins()
{
delete PluginManager::self();
}
void Application::destroyColorManager()
{
#ifdef KWIN_BUILD_CMS
delete ColorManager::self();
#endif
}
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<xcb_key_press_event_t*>(event)->time;
break;
case XCB_BUTTON_PRESS:
case XCB_BUTTON_RELEASE:
time = reinterpret_cast<xcb_button_press_event_t*>(event)->time;
break;
case XCB_MOTION_NOTIFY:
time = reinterpret_cast<xcb_motion_notify_event_t*>(event)->time;
break;
case XCB_ENTER_NOTIFY:
case XCB_LEAVE_NOTIFY:
time = reinterpret_cast<xcb_enter_notify_event_t*>(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<xcb_property_notify_event_t*>(event)->time;
break;
case XCB_SELECTION_CLEAR:
time = reinterpret_cast<xcb_selection_clear_event_t*>(event)->time;
break;
case XCB_SELECTION_REQUEST:
time = reinterpret_cast<xcb_selection_request_event_t*>(event)->time;
break;
case XCB_SELECTION_NOTIFY:
time = reinterpret_cast<xcb_selection_notify_event_t*>(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<xcb_shape_notify_event_t*>(event)->server_time;
}
if (eventType == Xcb::Extensions::self()->damageNotifyEvent()) {
time = reinterpret_cast<xcb_damage_notify_event_t*>(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<xcb_generic_event_t *>(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<Platform *>(plugin.instantiate());
if (m_platform) {
m_platform->setParent(this);
// 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);
}
}
}
emit platformCreated();
}
}
ApplicationWaylandAbstract::ApplicationWaylandAbstract(OperationMode mode, int &argc, char **argv)
: Application(mode, argc, argv)
{
}
ApplicationWaylandAbstract::~ApplicationWaylandAbstract()
{
}
} // namespace