Advance Wayland and KDE package bring-up

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,25 @@
target_sources(kwin PRIVATE
common.cpp
cursortheme.cpp
drm_format_helper.cpp
edid.cpp
filedescriptor.cpp
orientationsensor.cpp
ramfile.cpp
realtime.cpp
softwarevsyncmonitor.cpp
subsurfacemonitor.cpp
udev.cpp
vsyncmonitor.cpp
)
if (KWIN_BUILD_X11)
target_sources(kwin PRIVATE xcbutils.cpp)
endif()
if(CMAKE_SYSTEM_NAME MATCHES "Linux")
target_sources(kwin PRIVATE executable_path_proc.cpp)
elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
target_sources(kwin PRIVATE executable_path_sysctl.cpp)
else()
message(FATAL_ERROR "Unsupported platform ${CMAKE_SYSTEM_NAME}")
endif()
@@ -0,0 +1,30 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <memory>
namespace KWin
{
struct CDeleter
{
template<typename T>
void operator()(T *ptr)
{
if (ptr) {
free(ptr);
}
}
};
template<typename T>
using UniqueCPtr = std::unique_ptr<T, CDeleter>;
}
@@ -0,0 +1,283 @@
/*
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
*/
/*
This file is for (very) small utility functions/classes.
*/
#include "utils/common.h"
#include "utils/c_ptr.h"
#if KWIN_BUILD_X11
#include "effect/xcb.h"
#include <kkeyserver.h>
#endif
#include <QPainter>
#include <QWidget>
#ifndef KCMRULES
#include <QApplication>
#include <QDebug>
#endif
Q_LOGGING_CATEGORY(KWIN_CORE, "kwin_core", QtWarningMsg)
Q_LOGGING_CATEGORY(KWIN_OPENGL, "kwin_scene_opengl", QtWarningMsg)
Q_LOGGING_CATEGORY(KWIN_QPAINTER, "kwin_scene_qpainter", QtWarningMsg)
Q_LOGGING_CATEGORY(KWIN_VIRTUALKEYBOARD, "kwin_virtualkeyboard", QtWarningMsg)
namespace KWin
{
#ifndef KCMRULES
//************************************
// StrutRect
//************************************
StrutRect::StrutRect(QRect rect, StrutArea area)
: QRect(rect)
, m_area(area)
{
}
StrutRect::StrutRect(int x, int y, int width, int height, StrutArea area)
: QRect(x, y, width, height)
, m_area(area)
{
}
StrutRect::StrutRect(const StrutRect &other)
: QRect(other)
, m_area(other.area())
{
}
StrutRect &StrutRect::operator=(const StrutRect &other)
{
if (this != &other) {
QRect::operator=(other);
m_area = other.area();
}
return *this;
}
#if KWIN_BUILD_X11
static int server_grab_count = 0;
void grabXServer()
{
if (++server_grab_count == 1) {
xcb_grab_server(connection());
}
}
void ungrabXServer()
{
Q_ASSERT(server_grab_count > 0);
if (--server_grab_count == 0) {
xcb_ungrab_server(connection());
xcb_flush(connection());
}
}
static bool keyboard_grabbed = false;
bool grabXKeyboard(xcb_window_t w)
{
if (QWidget::keyboardGrabber() != nullptr) {
return false;
}
if (keyboard_grabbed) {
qCDebug(KWIN_CORE) << "Failed to grab X Keyboard: already grabbed by us";
return false;
}
if (qApp->activePopupWidget() != nullptr) {
qCDebug(KWIN_CORE) << "Failed to grab X Keyboard: no popup widget";
return false;
}
if (w == XCB_WINDOW_NONE) {
w = rootWindow();
}
const xcb_grab_keyboard_cookie_t c = xcb_grab_keyboard_unchecked(connection(), false, w, xTime(),
XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
UniqueCPtr<xcb_grab_keyboard_reply_t> grab(xcb_grab_keyboard_reply(connection(), c, nullptr));
if (!grab) {
qCDebug(KWIN_CORE) << "Failed to grab X Keyboard: grab null";
return false;
}
if (grab->status != XCB_GRAB_STATUS_SUCCESS) {
qCDebug(KWIN_CORE) << "Failed to grab X Keyboard: grab failed with status" << grab->status;
return false;
}
keyboard_grabbed = true;
return true;
}
void ungrabXKeyboard()
{
if (!keyboard_grabbed) {
// grabXKeyboard() may fail sometimes, so don't fail, but at least warn anyway
qCDebug(KWIN_CORE) << "ungrabXKeyboard() called but keyboard not grabbed!";
}
keyboard_grabbed = false;
xcb_ungrab_keyboard(connection(), XCB_TIME_CURRENT_TIME);
}
// converting between X11 mouse/keyboard state mask and Qt button/keyboard states
Qt::MouseButton x11ToQtMouseButton(int button)
{
if (button == XCB_BUTTON_INDEX_1) {
return Qt::LeftButton;
}
if (button == XCB_BUTTON_INDEX_2) {
return Qt::MiddleButton;
}
if (button == XCB_BUTTON_INDEX_3) {
return Qt::RightButton;
}
if (button == XCB_BUTTON_INDEX_4) {
return Qt::XButton1;
}
if (button == XCB_BUTTON_INDEX_5) {
return Qt::XButton2;
}
return Qt::NoButton;
}
Qt::MouseButtons x11ToQtMouseButtons(int state)
{
Qt::MouseButtons ret = {};
if (state & XCB_KEY_BUT_MASK_BUTTON_1) {
ret |= Qt::LeftButton;
}
if (state & XCB_KEY_BUT_MASK_BUTTON_2) {
ret |= Qt::MiddleButton;
}
if (state & XCB_KEY_BUT_MASK_BUTTON_3) {
ret |= Qt::RightButton;
}
if (state & XCB_KEY_BUT_MASK_BUTTON_4) {
ret |= Qt::XButton1;
}
if (state & XCB_KEY_BUT_MASK_BUTTON_5) {
ret |= Qt::XButton2;
}
return ret;
}
Qt::KeyboardModifiers x11ToQtKeyboardModifiers(int state)
{
Qt::KeyboardModifiers ret = {};
if (state & XCB_KEY_BUT_MASK_SHIFT) {
ret |= Qt::ShiftModifier;
}
if (state & XCB_KEY_BUT_MASK_CONTROL) {
ret |= Qt::ControlModifier;
}
if (state & KKeyServer::modXAlt()) {
ret |= Qt::AltModifier;
}
if (state & KKeyServer::modXMeta()) {
ret |= Qt::MetaModifier;
}
return ret;
}
#endif
#endif
QPointF popupOffset(const QRectF &anchorRect, const Qt::Edges anchorEdge, const Qt::Edges gravity, const QSizeF popupSize)
{
QPointF anchorPoint;
switch (anchorEdge & (Qt::LeftEdge | Qt::RightEdge)) {
case Qt::LeftEdge:
anchorPoint.setX(anchorRect.x());
break;
case Qt::RightEdge:
anchorPoint.setX(anchorRect.x() + anchorRect.width());
break;
default:
anchorPoint.setX(qRound(anchorRect.x() + anchorRect.width() / 2.0));
}
switch (anchorEdge & (Qt::TopEdge | Qt::BottomEdge)) {
case Qt::TopEdge:
anchorPoint.setY(anchorRect.y());
break;
case Qt::BottomEdge:
anchorPoint.setY(anchorRect.y() + anchorRect.height());
break;
default:
anchorPoint.setY(qRound(anchorRect.y() + anchorRect.height() / 2.0));
}
// calculate where the top left point of the popup will end up with the applied gravity
// gravity indicates direction. i.e if gravitating towards the top the popup's bottom edge
// will next to the anchor point
QPointF popupPosAdjust;
switch (gravity & (Qt::LeftEdge | Qt::RightEdge)) {
case Qt::LeftEdge:
popupPosAdjust.setX(-popupSize.width());
break;
case Qt::RightEdge:
popupPosAdjust.setX(0);
break;
default:
popupPosAdjust.setX(qRound(-popupSize.width() / 2.0));
}
switch (gravity & (Qt::TopEdge | Qt::BottomEdge)) {
case Qt::TopEdge:
popupPosAdjust.setY(-popupSize.height());
break;
case Qt::BottomEdge:
popupPosAdjust.setY(0);
break;
default:
popupPosAdjust.setY(qRound(-popupSize.height() / 2.0));
}
return anchorPoint + popupPosAdjust;
}
QRectF gravitateGeometry(const QRectF &rect, const QRectF &bounds, Gravity gravity)
{
QRectF geometry = rect;
switch (gravity) {
case Gravity::TopLeft:
geometry.moveRight(bounds.right());
geometry.moveBottom(bounds.bottom());
break;
case Gravity::Top:
case Gravity::TopRight:
geometry.moveLeft(bounds.left());
geometry.moveBottom(bounds.bottom());
break;
case Gravity::Right:
case Gravity::BottomRight:
case Gravity::Bottom:
case Gravity::None:
geometry.moveLeft(bounds.left());
geometry.moveTop(bounds.top());
break;
case Gravity::BottomLeft:
case Gravity::Left:
geometry.moveRight(bounds.right());
geometry.moveTop(bounds.top());
break;
}
return geometry;
}
} // namespace
@@ -0,0 +1,83 @@
/*
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
*/
#pragma once
// cmake stuff
#include "config-kwin.h"
// kwin
#include "effect/globals.h"
#include "utils/version.h"
// Qt
#include <QList>
#include <QLoggingCategory>
#include <QMatrix4x4>
#include <QPoint>
#include <QRect>
// system
#include <climits>
Q_DECLARE_LOGGING_CATEGORY(KWIN_CORE)
Q_DECLARE_LOGGING_CATEGORY(KWIN_OPENGL)
Q_DECLARE_LOGGING_CATEGORY(KWIN_QPAINTER)
Q_DECLARE_LOGGING_CATEGORY(KWIN_VIRTUALKEYBOARD)
namespace KWin
{
const QPoint invalidPoint(INT_MIN, INT_MIN);
enum StrutArea {
StrutAreaInvalid = 0, // Null
StrutAreaTop = 1 << 0,
StrutAreaRight = 1 << 1,
StrutAreaBottom = 1 << 2,
StrutAreaLeft = 1 << 3,
StrutAreaAll = StrutAreaTop | StrutAreaRight | StrutAreaBottom | StrutAreaLeft,
};
Q_DECLARE_FLAGS(StrutAreas, StrutArea)
class KWIN_EXPORT StrutRect : public QRect
{
public:
explicit StrutRect(QRect rect = QRect(), StrutArea area = StrutAreaInvalid);
StrutRect(int x, int y, int width, int height, StrutArea area = StrutAreaInvalid);
StrutRect(const StrutRect &other);
StrutRect &operator=(const StrutRect &other);
inline StrutArea area() const
{
return m_area;
}
private:
StrutArea m_area;
};
typedef QList<StrutRect> StrutRects;
enum ShadeMode {
ShadeNone, // not shaded
ShadeNormal, // normally shaded - isShade() is true only here
ShadeHover, // "shaded", but visible due to hover unshade
ShadeActivated // "shaded", but visible due to alt+tab to the window
};
#if KWIN_BUILD_X11
// converting between X11 mouse/keyboard state mask and Qt button/keyboard states
Qt::MouseButton x11ToQtMouseButton(int button);
Qt::MouseButton KWIN_EXPORT x11ToQtMouseButton(int button);
Qt::MouseButtons KWIN_EXPORT x11ToQtMouseButtons(int state);
Qt::KeyboardModifiers KWIN_EXPORT x11ToQtKeyboardModifiers(int state);
#endif
KWIN_EXPORT QRectF gravitateGeometry(const QRectF &rect, const QRectF &bounds, Gravity gravity);
} // namespace
// Must be outside namespace
Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::StrutAreas)
Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::QuickTileMode)
@@ -0,0 +1,329 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "utils/cursortheme.h"
#include "utils/svgcursorreader.h"
#include "utils/xcursorreader.h"
#include <KConfig>
#include <KConfigGroup>
#include <KShell>
#include <QDir>
#include <QSet>
#include <QSharedData>
#include <QStack>
#include <QStandardPaths>
namespace KWin
{
class CursorSpritePrivate : public QSharedData
{
public:
QImage data;
QPointF hotspot;
std::chrono::milliseconds delay;
};
struct CursorThemeXEntryInfo
{
QString path;
};
struct CursorThemeSvgEntryInfo
{
QString path;
};
using CursorThemeEntryInfo = std::variant<CursorThemeXEntryInfo,
CursorThemeSvgEntryInfo>;
class CursorThemeEntry
{
public:
explicit CursorThemeEntry(const CursorThemeEntryInfo &info);
void load(int size, qreal devicePixelRatio);
CursorThemeEntryInfo info;
QList<CursorSprite> sprites;
};
class CursorThemePrivate : public QSharedData
{
public:
CursorThemePrivate();
CursorThemePrivate(const QString &themeName, int size, qreal devicePixelRatio);
void discover(const QStringList &searchPaths);
void discoverXCursors(const QString &packagePath);
void discoverSvgCursors(const QString &packagePath);
QString name;
int size = 0;
qreal devicePixelRatio = 0;
QHash<QByteArray, std::shared_ptr<CursorThemeEntry>> registry;
};
CursorSprite::CursorSprite()
: d(new CursorSpritePrivate)
{
}
CursorSprite::CursorSprite(const CursorSprite &other)
: d(other.d)
{
}
CursorSprite::~CursorSprite()
{
}
CursorSprite &CursorSprite::operator=(const CursorSprite &other)
{
d = other.d;
return *this;
}
CursorSprite::CursorSprite(const QImage &data, const QPointF &hotspot, const std::chrono::milliseconds &delay)
: d(new CursorSpritePrivate)
{
d->data = data;
d->hotspot = hotspot;
d->delay = delay;
}
QImage CursorSprite::data() const
{
return d->data;
}
QPointF CursorSprite::hotspot() const
{
return d->hotspot;
}
std::chrono::milliseconds CursorSprite::delay() const
{
return d->delay;
}
CursorThemePrivate::CursorThemePrivate()
{
}
CursorThemePrivate::CursorThemePrivate(const QString &themeName, int size, qreal devicePixelRatio)
: name(themeName)
, size(size)
, devicePixelRatio(devicePixelRatio)
{
}
CursorThemeEntry::CursorThemeEntry(const CursorThemeEntryInfo &info)
: info(info)
{
}
void CursorThemeEntry::load(int size, qreal devicePixelRatio)
{
if (!sprites.isEmpty()) {
return;
}
if (const auto raster = std::get_if<CursorThemeXEntryInfo>(&info)) {
sprites = XCursorReader::load(raster->path, size, devicePixelRatio);
} else if (const auto svg = std::get_if<CursorThemeSvgEntryInfo>(&info)) {
sprites = SvgCursorReader::load(svg->path, size, devicePixelRatio);
}
}
void CursorThemePrivate::discoverXCursors(const QString &packagePath)
{
const QDir dir(packagePath);
QFileInfoList entries = dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
std::partition(entries.begin(), entries.end(), [](const QFileInfo &fileInfo) {
return !fileInfo.isSymLink();
});
for (const QFileInfo &entry : std::as_const(entries)) {
const QByteArray shape = QFile::encodeName(entry.fileName());
if (registry.contains(shape)) {
continue;
}
if (entry.isSymLink()) {
const QFileInfo symLinkInfo(entry.symLinkTarget());
if (symLinkInfo.absolutePath() == entry.absolutePath()) {
if (auto alias = registry.value(QFile::encodeName(symLinkInfo.fileName()))) {
registry.insert(shape, alias);
continue;
}
}
}
registry.insert(shape, std::make_shared<CursorThemeEntry>(CursorThemeXEntryInfo{
.path = entry.absoluteFilePath(),
}));
}
}
void CursorThemePrivate::discoverSvgCursors(const QString &packagePath)
{
const QDir dir(packagePath);
QFileInfoList entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
std::partition(entries.begin(), entries.end(), [](const QFileInfo &fileInfo) {
return !fileInfo.isSymLink();
});
for (const QFileInfo &entry : std::as_const(entries)) {
const QByteArray shape = QFile::encodeName(entry.fileName());
if (registry.contains(shape)) {
continue;
}
if (entry.isSymLink()) {
const QFileInfo symLinkInfo(entry.symLinkTarget());
if (symLinkInfo.absolutePath() == entry.absolutePath()) {
if (auto alias = registry.value(QFile::encodeName(symLinkInfo.fileName()))) {
registry.insert(shape, alias);
continue;
}
}
}
registry.insert(shape, std::make_shared<CursorThemeEntry>(CursorThemeSvgEntryInfo{
.path = entry.absoluteFilePath(),
}));
}
}
static QStringList defaultSearchPaths()
{
static QStringList paths;
if (paths.isEmpty()) {
if (const QString env = qEnvironmentVariable("XCURSOR_PATH"); !env.isEmpty()) {
const QStringList rawPaths = env.split(':', Qt::SkipEmptyParts);
for (const QString &rawPath : rawPaths) {
paths.append(KShell::tildeExpand(rawPath));
}
} else {
const QString home = QDir::homePath();
if (!home.isEmpty()) {
paths.append(home + QLatin1String("/.icons"));
}
const QStringList dataDirs = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
for (const QString &dataDir : dataDirs) {
paths.append(dataDir + QLatin1String("/icons"));
}
}
}
return paths;
}
void CursorThemePrivate::discover(const QStringList &searchPaths)
{
const QStringList paths = !searchPaths.isEmpty() ? searchPaths : defaultSearchPaths();
QStack<QString> stack;
QSet<QString> loaded;
stack.push(name);
while (!stack.isEmpty()) {
const QString themeName = stack.pop();
if (loaded.contains(themeName)) {
continue;
}
QStringList inherits;
for (const QString &path : paths) {
const QDir dir(path + QLatin1Char('/') + themeName);
if (!dir.exists()) {
continue;
}
if (const QDir package = dir.filePath(QLatin1String("cursors_scalable")); package.exists()) {
discoverSvgCursors(package.path());
} else if (const QDir package = dir.filePath(QLatin1String("cursors")); package.exists()) {
discoverXCursors(package.path());
}
if (inherits.isEmpty()) {
const KConfig config(dir.filePath(QStringLiteral("index.theme")), KConfig::NoGlobals);
inherits << KConfigGroup(&config, QStringLiteral("Icon Theme")).readEntry("Inherits", QStringList());
}
}
loaded.insert(themeName);
for (auto it = inherits.crbegin(); it != inherits.crend(); ++it) {
stack.push(*it);
}
}
}
CursorTheme::CursorTheme()
: d(new CursorThemePrivate)
{
}
CursorTheme::CursorTheme(const QString &themeName, int size, qreal devicePixelRatio, const QStringList &searchPaths)
: d(new CursorThemePrivate(themeName, size, devicePixelRatio))
{
d->discover(searchPaths);
}
CursorTheme::CursorTheme(const CursorTheme &other)
: d(other.d)
{
}
CursorTheme::~CursorTheme()
{
}
CursorTheme &CursorTheme::operator=(const CursorTheme &other)
{
d = other.d;
return *this;
}
bool CursorTheme::operator==(const CursorTheme &other)
{
return d == other.d;
}
bool CursorTheme::operator!=(const CursorTheme &other)
{
return !(*this == other);
}
QString CursorTheme::name() const
{
return d->name;
}
int CursorTheme::size() const
{
return d->size;
}
qreal CursorTheme::devicePixelRatio() const
{
return d->devicePixelRatio;
}
bool CursorTheme::isEmpty() const
{
return d->registry.isEmpty();
}
QList<CursorSprite> CursorTheme::shape(const QByteArray &name) const
{
if (auto entry = d->registry.value(name)) {
entry->load(d->size, d->devicePixelRatio);
return entry->sprites;
}
return QList<CursorSprite>();
}
} // namespace KWin
@@ -0,0 +1,142 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <kwin_export.h>
#include <QImage>
#include <QList>
#include <QSharedDataPointer>
#include <chrono>
namespace KWin
{
class CursorSpritePrivate;
class CursorThemePrivate;
/**
* The CursorSprite class represents a single sprite in the cursor theme.
*/
class KWIN_EXPORT CursorSprite
{
public:
/**
* Constructs an empty CursorSprite.
*/
CursorSprite();
/**
* Constructs a copy of the CursorSprite object @a other.
*/
CursorSprite(const CursorSprite &other);
/**
* Constructs an CursorSprite with the specified @a data, @a hotspot, and @a delay.
*/
CursorSprite(const QImage &data, const QPointF &hotspot, const std::chrono::milliseconds &delay);
/**
* Destructs the CursorSprite object.
*/
~CursorSprite();
/**
* Assigns the value of @a other to the cursor sprite object.
*/
CursorSprite &operator=(const CursorSprite &other);
/**
* Returns the image for this sprite.
*/
QImage data() const;
/**
* Returns the hotspot for this sprite. (0, 0) corresponds to the upper left corner.
*
* The coordinates of the hotspot are in device independent pixels.
*/
QPointF hotspot() const;
/**
* Returns the time interval between this sprite and the next one, in milliseconds.
*/
std::chrono::milliseconds delay() const;
private:
QSharedDataPointer<CursorSpritePrivate> d;
};
/**
* The CursorTheme class represents a cursor theme.
*/
class KWIN_EXPORT CursorTheme
{
public:
/**
* Constructs an empty cursor theme.
*/
CursorTheme();
/**
* Loads the cursor theme with the given @ themeName and the desired @a size.
* The @a dpr specifies the desired scale factor. If no theme with the provided
* name exists, the cursor theme will be empty.
*
* @a searchPaths specifies where the cursor theme should be looked for.
*/
CursorTheme(const QString &theme, int size, qreal devicePixelRatio, const QStringList &searchPaths = QStringList());
/**
* Constructs a copy of the CursorTheme object @a other.
*/
CursorTheme(const CursorTheme &other);
/**
* Destructs the CursorTheme object.
*/
~CursorTheme();
/**
* Assigns the value of @a other to the cursor theme object.
*/
CursorTheme &operator=(const CursorTheme &other);
bool operator==(const CursorTheme &other);
bool operator!=(const CursorTheme &other);
/**
* The name of the requested cursor theme.
*/
QString name() const;
/**
* The size of the requested cursor theme.
*/
int size() const;
/**
* The scale factor of the requested cursor theme.
*/
qreal devicePixelRatio() const;
/**
* Returns @c true if the cursor theme is empty; otherwise returns @c false.
*/
bool isEmpty() const;
/**
* Returns the list of cursor sprites for the cursor with the given @a name.
*/
QList<CursorSprite> shape(const QByteArray &name) const;
private:
QSharedDataPointer<CursorThemePrivate> d;
};
} // namespace KWin
@@ -0,0 +1,89 @@
/*
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kwin_export.h"
#include <QList>
#include <QRegion>
namespace KWin
{
/**
* The DamageJournal class is a helper that tracks last N damage regions.
*/
class KWIN_EXPORT DamageJournal
{
public:
/**
* Returns the maximum number of damage regions that can be stored in the journal.
*/
int capacity() const
{
return m_capacity;
}
/**
* Sets the maximum number of damage regions that can be stored in the journal
* to @a capacity.
*/
void setCapacity(int capacity)
{
m_capacity = capacity;
}
/**
* Adds the specified @a region to the journal.
*/
void add(const QRegion &region)
{
while (m_log.size() >= m_capacity) {
m_log.removeLast();
}
m_log.prepend(region);
}
/**
* Clears the damage journal. Typically, one would want to clear the damage journal
* if a buffer swap fails for some reason.
*/
void clear()
{
m_log.clear();
}
/**
* Accumulates the damage regions in the log up to the specified @a bufferAge.
*
* If the specified buffer age value refers to a damage region older than the last
* one in the journal, @a fallback will be returned.
*/
QRegion accumulate(int bufferAge, const QRegion &fallback = QRegion()) const
{
QRegion region;
if (bufferAge > 0 && bufferAge <= m_log.size()) {
for (int i = 0; i < bufferAge - 1; ++i) {
region += m_log[i];
}
} else {
region = fallback;
}
return region;
}
QRegion lastDamage() const
{
return m_log.first();
}
private:
QList<QRegion> m_log;
int m_capacity = 10;
};
} // namespace KWin
@@ -0,0 +1,155 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_format_helper.h"
namespace KWin
{
std::optional<FormatInfo> FormatInfo::get(uint32_t drmFormat)
{
switch (drmFormat) {
case DRM_FORMAT_XRGB8888:
case DRM_FORMAT_XBGR8888:
case DRM_FORMAT_RGBX8888:
case DRM_FORMAT_BGRX8888:
return FormatInfo{
.drmFormat = drmFormat,
.bitsPerColor = 8,
.alphaBits = 0,
.bitsPerPixel = 32,
.openglFormat = GL_RGBA8,
.floatingPoint = false,
};
case DRM_FORMAT_ARGB8888:
case DRM_FORMAT_ABGR8888:
case DRM_FORMAT_RGBA8888:
case DRM_FORMAT_BGRA8888:
return FormatInfo{
.drmFormat = drmFormat,
.bitsPerColor = 8,
.alphaBits = 8,
.bitsPerPixel = 32,
.openglFormat = GL_RGBA8,
.floatingPoint = false,
};
case DRM_FORMAT_XRGB2101010:
case DRM_FORMAT_XBGR2101010:
case DRM_FORMAT_RGBX1010102:
case DRM_FORMAT_BGRX1010102:
return FormatInfo{
.drmFormat = drmFormat,
.bitsPerColor = 10,
.alphaBits = 0,
.bitsPerPixel = 32,
.openglFormat = GL_RGB10_A2,
.floatingPoint = false,
};
case DRM_FORMAT_ARGB2101010:
case DRM_FORMAT_ABGR2101010:
case DRM_FORMAT_RGBA1010102:
case DRM_FORMAT_BGRA1010102:
return FormatInfo{
.drmFormat = drmFormat,
.bitsPerColor = 10,
.alphaBits = 2,
.bitsPerPixel = 32,
.openglFormat = GL_RGB10_A2,
.floatingPoint = false,
};
case DRM_FORMAT_XRGB16161616:
case DRM_FORMAT_XBGR16161616:
return FormatInfo{
.drmFormat = drmFormat,
.bitsPerColor = 16,
.alphaBits = 0,
.bitsPerPixel = 64,
.openglFormat = GL_RGBA16,
.floatingPoint = false,
};
case DRM_FORMAT_ARGB16161616:
case DRM_FORMAT_ABGR16161616:
return FormatInfo{
.drmFormat = drmFormat,
.bitsPerColor = 16,
.alphaBits = 16,
.bitsPerPixel = 64,
.openglFormat = GL_RGBA16,
.floatingPoint = false,
};
case DRM_FORMAT_XRGB16161616F:
case DRM_FORMAT_XBGR16161616F:
return FormatInfo{
.drmFormat = drmFormat,
.bitsPerColor = 16,
.alphaBits = 0,
.bitsPerPixel = 64,
.openglFormat = GL_RGBA16F,
.floatingPoint = true,
};
case DRM_FORMAT_ARGB16161616F:
case DRM_FORMAT_ABGR16161616F:
return FormatInfo{
.drmFormat = drmFormat,
.bitsPerColor = 16,
.alphaBits = 16,
.bitsPerPixel = 64,
.openglFormat = GL_RGBA16F,
.floatingPoint = true,
};
case DRM_FORMAT_ARGB4444:
case DRM_FORMAT_ABGR4444:
case DRM_FORMAT_RGBA4444:
case DRM_FORMAT_BGRA4444:
return FormatInfo{
.drmFormat = drmFormat,
.bitsPerColor = 4,
.alphaBits = 4,
.bitsPerPixel = 16,
.openglFormat = GL_RGBA4,
.floatingPoint = false,
};
case DRM_FORMAT_ARGB1555:
case DRM_FORMAT_ABGR1555:
case DRM_FORMAT_RGBA5551:
case DRM_FORMAT_BGRA5551:
return FormatInfo{
.drmFormat = drmFormat,
.bitsPerColor = 5,
.alphaBits = 1,
.bitsPerPixel = 16,
.openglFormat = GL_RGB5_A1,
.floatingPoint = false,
};
case DRM_FORMAT_NV12:
return FormatInfo{
.drmFormat = drmFormat,
.bitsPerColor = 8,
.alphaBits = 0,
.bitsPerPixel = 24,
.openglFormat = GL_R8,
.floatingPoint = false,
};
default:
return std::nullopt;
}
}
QString FormatInfo::drmFormatName(uint32_t format)
{
return QString::asprintf(
"%c%c%c%c %s-endian (0x%08x)",
QLatin1Char(format & 0xff).toLatin1(),
QLatin1Char((format >> 8) & 0xff).toLatin1(),
QLatin1Char((format >> 16) & 0xff).toLatin1(),
QLatin1Char((format >> 24) & 0x7f).toLatin1(),
format & DRM_FORMAT_BIG_ENDIAN ? "big" : "little",
format);
}
}
@@ -0,0 +1,59 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kwin_export.h"
#include <QHash>
#include <QList>
#include <QString>
#include <epoxy/gl.h>
#include <libdrm/drm_fourcc.h>
#include <optional>
#include <stdint.h>
namespace KWin
{
struct YuvFormat
{
uint32_t format = DRM_FORMAT_YUYV;
uint32_t widthDivisor = 1;
uint32_t heightDivisor = 1;
};
struct YuvConversion
{
QList<struct YuvFormat> plane = {};
};
static const QHash<uint32_t, YuvConversion> s_drmConversions = {
{DRM_FORMAT_NV12, YuvConversion{
{YuvFormat{DRM_FORMAT_R8, 1, 1}, YuvFormat{DRM_FORMAT_GR88, 2, 2}},
}},
};
struct KWIN_EXPORT FormatInfo
{
uint32_t drmFormat;
uint32_t bitsPerColor;
uint32_t alphaBits;
uint32_t bitsPerPixel;
GLint openglFormat;
bool floatingPoint;
std::optional<YuvConversion> yuvConversion() const
{
const auto it = s_drmConversions.find(drmFormat);
return it != s_drmConversions.end() ? *it : std::optional<YuvConversion>{};
}
static std::optional<FormatInfo> get(uint32_t drmFormat);
static QString drmFormatName(uint32_t format);
};
}
@@ -0,0 +1,403 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Flöser <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "edid.h"
#include "config-kwin.h"
#include "c_ptr.h"
#include "common.h"
#include <QFile>
#include <QStandardPaths>
#include <cstdlib>
#include <KLocalizedString>
#include <QCryptographicHash>
extern "C" {
#include <libdisplay-info/cta.h>
#include <libdisplay-info/displayid.h>
#include <libdisplay-info/edid.h>
#include <libdisplay-info/info.h>
}
namespace KWin
{
static QByteArray parsePnpId(const uint8_t *data)
{
// Decode PNP ID from three 5 bit words packed into 2 bytes:
//
// | Byte | Bit |
// | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
// ----------------------------------------
// | 1 | 0)| (4| 3 | 2 | 1 | 0)| (4| 3 |
// | | * | Character 1 | Char 2|
// ----------------------------------------
// | 2 | 2 | 1 | 0)| (4| 3 | 2 | 1 | 0)|
// | | Character2| Character 3 |
// ----------------------------------------
const uint offset = 0x8;
char pnpId[4];
pnpId[0] = 'A' + ((data[offset + 0] >> 2) & 0x1f) - 1;
pnpId[1] = 'A' + (((data[offset + 0] & 0x3) << 3) | ((data[offset + 1] >> 5) & 0x7)) - 1;
pnpId[2] = 'A' + (data[offset + 1] & 0x1f) - 1;
pnpId[3] = '\0';
return QByteArray(pnpId);
}
static QByteArray parseEisaId(const uint8_t *data)
{
for (int i = 72; i <= 108; i += 18) {
// Skip the block if it isn't used as monitor descriptor.
if (data[i]) {
continue;
}
if (data[i + 1]) {
continue;
}
// We have found the EISA ID, it's stored as ASCII.
if (data[i + 3] == 0xfe) {
return QByteArray(reinterpret_cast<const char *>(&data[i + 5]), 13).trimmed();
}
}
// If there isn't an ASCII EISA ID descriptor, try to decode PNP ID
return parsePnpId(data);
}
static QByteArray parseVendor(const uint8_t *data)
{
const auto pnpId = parsePnpId(data);
// Map to vendor name
QFile pnpFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("hwdata/pnp.ids")));
if (pnpFile.exists() && pnpFile.open(QIODevice::ReadOnly)) {
while (!pnpFile.atEnd()) {
const auto line = pnpFile.readLine();
if (line.startsWith(pnpId)) {
return line.mid(4).trimmed();
}
}
}
return {};
}
static QSize determineScreenPhysicalSizeMm(const di_edid *edid)
{
// An EDID can contain zero or more detailed timing definitions, which can
// contain more precise physical dimensions (in millimeters, as opposed to
// centimeters). Pick the first sane physical dimension from detailed timings
// and fall back to the basic dimensions.
const struct di_edid_detailed_timing_def *const *detailedTimings = di_edid_get_detailed_timing_defs(edid);
// detailedTimings is a null-terminated array.
for (int i = 0; detailedTimings[i] != nullptr; i++) {
const struct di_edid_detailed_timing_def *timing = detailedTimings[i];
// Sanity check dimensions: physical aspect ratio should roughly equal
// mode aspect ratio (i.e. width_in_pixels / height_in_pixels).
// This assumes that the display has square pixels, but this is true for
// basically all modern displays.
if (timing->horiz_image_mm > 0 && timing->vert_image_mm > 0
&& timing->horiz_video > 0 && timing->vert_video > 0) {
const double physicalAspectRatio = double(timing->horiz_image_mm) / double(timing->vert_image_mm);
const double modeAspectRatio = double(timing->horiz_video) / double(timing->vert_video);
if (std::abs(physicalAspectRatio - modeAspectRatio) <= 0.1) {
return QSize(timing->horiz_image_mm, timing->vert_image_mm);
}
}
}
const di_edid_screen_size *screenSize = di_edid_get_screen_size(edid);
return QSize(screenSize->width_cm, screenSize->height_cm) * 10;
}
Edid::Edid()
{
}
Edid::Edid(const void *data, uint32_t size)
: Edid(QByteArrayView(reinterpret_cast<const uint8_t *>(data), size))
{
}
Edid::Edid(QByteArrayView data, std::optional<QByteArrayView> identifierOverride)
: Edid(data)
{
if (identifierOverride.has_value()) {
m_identifier = identifierOverride->toByteArray();
}
}
static const auto s_forceHdrSupport = []() -> std::optional<bool> {
bool ok = false;
int ret = qEnvironmentVariableIntValue("KWIN_FORCE_ASSUME_HDR_SUPPORT", &ok);
if (ok) {
return ret == 1;
} else {
return std::nullopt;
}
}();
Edid::Edid(QByteArrayView data)
{
m_raw = QByteArray(data.data(), data.size());
if (m_raw.isEmpty()) {
return;
}
QCryptographicHash hash(QCryptographicHash::Md5);
hash.addData(m_raw);
m_hash = QString::fromLatin1(hash.result().toHex());
auto info = di_info_parse_edid(data.data(), data.size());
if (!info) {
qCWarning(KWIN_CORE, "parsing edid failed");
return;
}
const di_edid *edid = di_info_get_edid(info);
const di_edid_vendor_product *productInfo = di_edid_get_vendor_product(edid);
const uint8_t *bytes = reinterpret_cast<const uint8_t *>(data.data());
// basic output information
m_physicalSize = determineScreenPhysicalSizeMm(edid);
m_eisaId = parseEisaId(bytes);
UniqueCPtr<char> monitorName{di_info_get_model(info)};
m_monitorName = QByteArray(monitorName.get());
UniqueCPtr<char> serial{di_info_get_serial(info)};
m_serialNumber = QByteArray(serial.get());
m_vendor = parseVendor(bytes);
m_identifier = QByteArray(productInfo->manufacturer, 3) + " " + QByteArray::number(productInfo->product) + " " + QByteArray::number(productInfo->serial) + " "
+ QByteArray::number(productInfo->manufacture_week) + " " + QByteArray::number(productInfo->manufacture_year) + " " + QByteArray::number(productInfo->model_year);
// colorimetry and HDR metadata
const auto chromaticity = di_edid_get_chromaticity_coords(edid);
if (chromaticity) {
const xy red{chromaticity->red_x, chromaticity->red_y};
const xy green{chromaticity->green_x, chromaticity->green_y};
const xy blue{chromaticity->blue_x, chromaticity->blue_y};
const xy white{chromaticity->white_x, chromaticity->white_y};
if (Colorimetry::isReal(red, green, blue, white)) {
m_colorimetry = Colorimetry{
red,
green,
blue,
white,
};
} else {
qCWarning(KWIN_CORE) << "EDID colorimetry" << red << green << blue << white << "is is invalid";
}
} else {
m_colorimetry.reset();
}
const di_edid_cta *cta = nullptr;
const di_displayid *displayid = nullptr;
const di_edid_ext *const *exts = di_edid_get_extensions(edid);
const di_cta_hdr_static_metadata_block *hdr_static_metadata = nullptr;
const di_cta_colorimetry_block *colorimetry = nullptr;
for (; *exts != nullptr; exts++) {
if (!cta && (cta = di_edid_ext_get_cta(*exts))) {
continue;
}
if (!displayid && (displayid = di_edid_ext_get_displayid(*exts))) {
continue;
}
}
if (cta) {
const di_cta_data_block *const *blocks = di_edid_cta_get_data_blocks(cta);
for (; *blocks != nullptr; blocks++) {
if (!hdr_static_metadata && (hdr_static_metadata = di_cta_data_block_get_hdr_static_metadata(*blocks))) {
continue;
}
if (!colorimetry && (colorimetry = di_cta_data_block_get_colorimetry(*blocks))) {
continue;
}
}
if (hdr_static_metadata) {
m_hdrMetadata = HDRMetadata{
.desiredContentMinLuminance = hdr_static_metadata->desired_content_min_luminance,
.desiredContentMaxLuminance = hdr_static_metadata->desired_content_max_luminance > 0 ? std::make_optional(hdr_static_metadata->desired_content_max_luminance) : std::nullopt,
.desiredMaxFrameAverageLuminance = hdr_static_metadata->desired_content_max_frame_avg_luminance > 0 ? std::make_optional(hdr_static_metadata->desired_content_max_frame_avg_luminance) : std::nullopt,
.supportsPQ = hdr_static_metadata->eotfs->pq,
.supportsBT2020 = colorimetry && colorimetry->bt2020_rgb,
};
}
}
if (s_forceHdrSupport.has_value()) {
if (!m_hdrMetadata) {
m_hdrMetadata = HDRMetadata{
.desiredContentMinLuminance = 0,
.desiredContentMaxLuminance = std::nullopt,
.desiredMaxFrameAverageLuminance = std::nullopt,
.supportsPQ = *s_forceHdrSupport,
.supportsBT2020 = *s_forceHdrSupport,
};
} else {
m_hdrMetadata->supportsPQ = *s_forceHdrSupport;
m_hdrMetadata->supportsBT2020 = *s_forceHdrSupport;
}
}
if (displayid) {
const di_displayid_display_params *params = nullptr;
const di_displayid_type_i_ii_vii_timing *const *type1Timings = nullptr;
const di_displayid_type_i_ii_vii_timing *const *type2Timings = nullptr;
for (auto block = di_displayid_get_data_blocks(displayid); *block != nullptr; block++) {
if (!params && (params = di_displayid_data_block_get_display_params(*block))) {
continue;
}
if (!type1Timings && (type1Timings = di_displayid_data_block_get_type_i_timings(*block))) {
continue;
}
if (!type2Timings && (type2Timings = di_displayid_data_block_get_type_ii_timings(*block))) {
continue;
}
}
if (params && params->horiz_pixels != 0 && params->vert_pixels != 0) {
m_nativeResolution = QSize(params->horiz_pixels, params->vert_pixels);
}
if (type1Timings && !m_nativeResolution) {
for (auto timing = type1Timings; *timing != nullptr; timing++) {
if ((*timing)->preferred && (!m_nativeResolution || m_nativeResolution->width() < (*timing)->horiz_active || m_nativeResolution->height() < (*timing)->vert_active)) {
m_nativeResolution = QSize((*timing)->horiz_active, (*timing)->vert_active);
}
}
}
if (type2Timings && !m_nativeResolution) {
for (auto timing = type2Timings; *timing != nullptr; timing++) {
if ((*timing)->preferred && (!m_nativeResolution || m_nativeResolution->width() < (*timing)->horiz_active || m_nativeResolution->height() < (*timing)->vert_active)) {
m_nativeResolution = QSize((*timing)->horiz_active, (*timing)->vert_active);
}
}
}
}
// EDID often contains misleading information for backwards compatibility
// so only use it if we don't have the same info from DisplayID
if (const auto misc = di_edid_get_misc_features(edid); misc && !m_nativeResolution) {
if (misc->preferred_timing_is_native) {
const auto timing = di_edid_get_detailed_timing_defs(edid);
if (*timing != nullptr) {
m_nativeResolution = QSize((*timing)->horiz_video, (*timing)->vert_video);
}
}
}
m_isValid = true;
di_info_destroy(info);
}
std::optional<QSize> Edid::likelyNativeResolution() const
{
return m_nativeResolution;
}
bool Edid::isValid() const
{
return m_isValid;
}
QSize Edid::physicalSize() const
{
return m_physicalSize;
}
QByteArray Edid::eisaId() const
{
return m_eisaId;
}
QByteArray Edid::monitorName() const
{
return m_monitorName;
}
QByteArray Edid::serialNumber() const
{
return m_serialNumber;
}
QByteArray Edid::vendor() const
{
return m_vendor;
}
QByteArray Edid::raw() const
{
return m_raw;
}
QString Edid::manufacturerString() const
{
QString manufacturer;
if (!m_vendor.isEmpty()) {
manufacturer = QString::fromLatin1(m_vendor);
} else if (!m_eisaId.isEmpty()) {
manufacturer = QString::fromLatin1(m_eisaId);
}
return manufacturer;
}
QString Edid::nameString() const
{
if (!m_monitorName.isEmpty()) {
return QString::fromLatin1(m_monitorName);
} else if (!m_serialNumber.isEmpty()) {
return QString::fromLatin1(m_serialNumber);
} else {
return i18n("unknown");
}
}
QString Edid::hash() const
{
return m_hash;
}
std::optional<Colorimetry> Edid::colorimetry() const
{
return m_colorimetry;
}
double Edid::desiredMinLuminance() const
{
return m_hdrMetadata ? m_hdrMetadata->desiredContentMinLuminance : 0;
}
std::optional<double> Edid::desiredMaxFrameAverageLuminance() const
{
return m_hdrMetadata ? m_hdrMetadata->desiredMaxFrameAverageLuminance : std::nullopt;
}
std::optional<double> Edid::desiredMaxLuminance() const
{
return m_hdrMetadata ? m_hdrMetadata->desiredContentMaxLuminance : std::nullopt;
}
bool Edid::supportsPQ() const
{
return m_hdrMetadata && m_hdrMetadata->supportsPQ;
}
bool Edid::supportsBT2020() const
{
return m_hdrMetadata && m_hdrMetadata->supportsBT2020;
}
QByteArray Edid::identifier() const
{
return m_identifier;
}
} // namespace KWin
@@ -0,0 +1,134 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/colorspace.h"
#include "kwin_export.h"
#include <QByteArray>
#include <QList>
#include <QSize>
#include <QVector2D>
namespace KWin
{
/**
* Helper class that can be used for parsing EDID blobs.
*
* http://read.pudn.com/downloads110/ebook/456020/E-EDID%20Standard.pdf
*/
class KWIN_EXPORT Edid
{
public:
Edid();
explicit Edid(const void *data, uint32_t size);
explicit Edid(QByteArrayView data);
/**
* for testing purpose, optionally overrides the identifier with the specified value
*/
explicit Edid(QByteArrayView data, std::optional<QByteArrayView> identifierOverride);
/**
* Whether this instance of EDID is valid.
*/
bool isValid() const;
/**
* Returns physical dimensions of the monitor, in millimeters.
*/
QSize physicalSize() const;
/**
* Returns EISA ID of the manufacturer of the monitor.
*/
QByteArray eisaId() const;
/**
* Returns the product name of the monitor.
*/
QByteArray monitorName() const;
/**
* Returns the serial number of the monitor.
*/
QByteArray serialNumber() const;
/**
* Returns the name of the vendor.
*/
QByteArray vendor() const;
/**
* Returns the raw edid
*/
QByteArray raw() const;
/**
* returns the vendor if included, the EISA ID if not
*/
QString manufacturerString() const;
/**
* returns a string representing the monitor name
* Can be a serial number or "unknown" if the name is empty
*/
QString nameString() const;
QString hash() const;
std::optional<Colorimetry> colorimetry() const;
double desiredMinLuminance() const;
std::optional<double> desiredMaxFrameAverageLuminance() const;
std::optional<double> desiredMaxLuminance() const;
bool supportsPQ() const;
bool supportsBT2020() const;
/**
* @returns a string that is intended to identify the monitor uniquely.
* Note that multiple monitors can have the same EDID, so this is not always actually unique
*/
QByteArray identifier() const;
/**
* @returns the resolution that's most likely native. This is unreliable, because
* - some displays provide the wrong information
* - libdisplay-info doesn't parse all the DisplayID things yet
* so it should only be used as a fallback, when the kernel doesn't provide a preferred mode
*/
std::optional<QSize> likelyNativeResolution() const;
private:
QSize m_physicalSize;
QByteArray m_vendor;
QByteArray m_eisaId;
QByteArray m_monitorName;
QByteArray m_serialNumber;
QString m_hash;
std::optional<Colorimetry> m_colorimetry;
struct HDRMetadata
{
double desiredContentMinLuminance;
std::optional<double> desiredContentMaxLuminance;
std::optional<double> desiredMaxFrameAverageLuminance;
bool supportsPQ;
bool supportsBT2020;
};
std::optional<HDRMetadata> m_hdrMetadata;
std::optional<QSize> m_nativeResolution;
QByteArray m_identifier;
QByteArray m_raw;
bool m_isValid = false;
};
} // namespace KWin
@@ -0,0 +1,13 @@
/*
SPDX-FileCopyrightText: 2021 Tobias C. Berner <tcberner@FreeBSD.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#pragma once
#include "kwin_export.h"
#include <QString>
KWIN_EXPORT QString executablePathFromPid(pid_t);
@@ -0,0 +1,14 @@
/*
SPDX-FileCopyrightText: 2021 Tobias C. Berner <tcberner@FreeBSD.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "executable_path.h"
#include <QFileInfo>
QString executablePathFromPid(pid_t pid)
{
return QFileInfo(QStringLiteral("/proc/%1/exe").arg(pid)).symLinkTarget();
}
@@ -0,0 +1,22 @@
/*
SPDX-FileCopyrightText: 2021 Tobias C. Berner <tcberner@FreeBSD.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include "executable_path.h"
QString executablePathFromPid(pid_t pid)
{
const int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, static_cast<int>(pid)};
char buf[MAXPATHLEN];
size_t cb = sizeof(buf);
if (sysctl(mib, 4, buf, &cb, nullptr, 0) == 0) {
return QString::fromLocal8Bit(realpath(buf, nullptr));
}
return QString();
}
@@ -0,0 +1,109 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText:: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "filedescriptor.h"
#include <fcntl.h>
#include <sys/poll.h>
#include <unistd.h>
#include <utility>
namespace KWin
{
FileDescriptor::FileDescriptor(int fd)
: m_fd(fd)
{
}
FileDescriptor::FileDescriptor(FileDescriptor &&other)
: m_fd(std::exchange(other.m_fd, -1))
{
}
FileDescriptor &FileDescriptor::operator=(FileDescriptor &&other)
{
if (m_fd != -1) {
::close(m_fd);
}
m_fd = std::exchange(other.m_fd, -1);
return *this;
}
FileDescriptor::~FileDescriptor()
{
if (m_fd != -1) {
::close(m_fd);
}
}
bool FileDescriptor::isValid() const
{
return m_fd != -1;
}
int FileDescriptor::get() const
{
return m_fd;
}
int FileDescriptor::take()
{
return std::exchange(m_fd, -1);
}
void FileDescriptor::reset()
{
if (m_fd != -1) {
::close(m_fd);
m_fd = -1;
}
}
FileDescriptor FileDescriptor::duplicate() const
{
if (m_fd != -1) {
return FileDescriptor{fcntl(m_fd, F_DUPFD_CLOEXEC, 0)};
} else {
return {};
}
}
bool FileDescriptor::isClosed() const
{
return isClosed(m_fd);
}
bool FileDescriptor::isReadable() const
{
return isReadable(m_fd);
}
bool FileDescriptor::isClosed(int fd)
{
pollfd pfd = {
.fd = fd,
.events = POLLIN,
.revents = 0,
};
if (poll(&pfd, 1, 0) < 0) {
return true;
}
return pfd.revents & (POLLHUP | POLLERR);
}
bool FileDescriptor::isReadable(int fd)
{
pollfd pfd = {
.fd = fd,
.events = POLLIN,
.revents = 0,
};
return poll(&pfd, 1, 0) && (pfd.revents & (POLLIN | POLLNVAL)) != 0;
}
}
@@ -0,0 +1,41 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText:: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <kwin_export.h>
namespace KWin
{
class KWIN_EXPORT FileDescriptor
{
public:
FileDescriptor() = default;
explicit FileDescriptor(int fd);
FileDescriptor(FileDescriptor &&);
FileDescriptor &operator=(FileDescriptor &&);
~FileDescriptor();
bool isValid() const;
int get() const;
int take();
void reset();
FileDescriptor duplicate() const;
bool isReadable() const;
bool isClosed() const;
static bool isReadable(int fd);
static bool isClosed(int fd);
private:
int m_fd = -1;
};
}
@@ -0,0 +1,28 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "utils/version.h"
#include <sys/utsname.h>
namespace KWin
{
inline static Version linuxKernelVersion()
{
struct utsname name;
uname(&name);
if (qstrcmp(name.sysname, "Linux") == 0) {
return Version::parseString(name.release);
}
return Version(0, 0, 0);
}
}
@@ -0,0 +1,38 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2024 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QInputEvent>
#include <array>
namespace KWin
{
static constexpr std::array s_mediaKeys = {
Qt::Key::Key_MediaLast,
Qt::Key::Key_MediaNext,
Qt::Key::Key_MediaPause,
Qt::Key::Key_MediaPlay,
Qt::Key::Key_MediaPrevious,
Qt::Key::Key_MediaRecord,
Qt::Key::Key_MediaStop,
Qt::Key::Key_MediaTogglePlayPause,
Qt::Key::Key_VolumeUp,
Qt::Key::Key_VolumeDown,
Qt::Key::Key_VolumeMute,
Qt::Key::Key_MicVolumeUp,
Qt::Key::Key_MicVolumeDown,
Qt::Key::Key_MicMute,
};
static inline bool isMediaKey(int key)
{
return std::ranges::find(s_mediaKeys, key) != s_mediaKeys.end();
}
}
@@ -0,0 +1,73 @@
/*
SPDX-FileCopyrightText: 2023 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <sys/mman.h>
#include <utility>
namespace KWin
{
class MemoryMap
{
public:
MemoryMap()
: m_data(MAP_FAILED)
, m_size(0)
{
}
MemoryMap(int size, int prot, int flags, int fd, off_t offset)
: m_data(mmap(nullptr, size, prot, flags, fd, offset))
, m_size(size)
{
}
MemoryMap(MemoryMap &&other)
: m_data(std::exchange(other.m_data, MAP_FAILED))
, m_size(std::exchange(other.m_size, 0))
{
}
~MemoryMap()
{
if (m_data != MAP_FAILED) {
munmap(m_data, m_size);
}
}
MemoryMap &operator=(MemoryMap &&other)
{
if (m_data != MAP_FAILED) {
munmap(m_data, m_size);
}
m_data = std::exchange(other.m_data, MAP_FAILED);
m_size = std::exchange(other.m_size, 0);
return *this;
}
inline bool isValid() const
{
return m_data != MAP_FAILED;
}
inline void *data() const
{
return m_data;
}
inline int size() const
{
return m_size;
}
private:
void *m_data;
int m_size;
};
} // namespace KWin
@@ -0,0 +1,54 @@
/*
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "orientationsensor.h"
#include <QOrientationSensor>
namespace KWin
{
OrientationSensor::OrientationSensor()
: m_sensor(std::make_unique<QOrientationSensor>())
, m_reading(std::make_unique<QOrientationReading>())
{
m_reading->setOrientation(QOrientationReading::Orientation::Undefined);
}
OrientationSensor::~OrientationSensor() = default;
void OrientationSensor::setEnabled(bool enable)
{
if (enable) {
connect(m_sensor.get(), &QOrientationSensor::readingChanged, this, &OrientationSensor::update, Qt::UniqueConnection);
m_sensor->start();
} else {
disconnect(m_sensor.get(), &QOrientationSensor::readingChanged, this, &OrientationSensor::update);
m_reading->setOrientation(QOrientationReading::Undefined);
}
}
QOrientationReading *OrientationSensor::reading() const
{
return m_reading.get();
}
void OrientationSensor::update()
{
if (auto reading = m_sensor->reading()) {
if (m_reading->orientation() != reading->orientation()) {
m_reading->setOrientation(reading->orientation());
Q_EMIT orientationChanged();
}
} else if (m_reading->orientation() != QOrientationReading::Orientation::Undefined) {
m_reading->setOrientation(QOrientationReading::Orientation::Undefined);
Q_EMIT orientationChanged();
}
}
}
#include "moc_orientationsensor.cpp"
@@ -0,0 +1,38 @@
/*
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QObject>
#include <memory>
class QOrientationSensor;
class QOrientationReading;
namespace KWin
{
class OrientationSensor : public QObject
{
Q_OBJECT
public:
explicit OrientationSensor();
~OrientationSensor();
void setEnabled(bool enable);
QOrientationReading *reading() const;
Q_SIGNALS:
void orientationChanged();
private:
void update();
const std::unique_ptr<QOrientationSensor> m_sensor;
const std::unique_ptr<QOrientationReading> m_reading;
};
}
@@ -0,0 +1,36 @@
/*
SPDX-FileCopyrightText: 2024 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "utils/filedescriptor.h"
#include <optional>
#include <unistd.h>
namespace KWin
{
struct Pipe
{
FileDescriptor fds[2];
static std::optional<Pipe> create(int flags)
{
int fds[2];
if (pipe2(fds, flags) != 0) {
return std::nullopt;
}
return Pipe{
.fds = {
FileDescriptor(fds[0]),
FileDescriptor(fds[1]),
},
};
}
};
} // namespace KWin
@@ -0,0 +1,164 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2022 MBition GmbH
SPDX-FileContributor: Kai Uwe Broulik <kai_uwe.broulik@mbition.io>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "ramfile.h"
#include "common.h" // for logging
#include <QScopeGuard>
#include <cerrno>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <utility>
namespace KWin
{
RamFile::RamFile(const char *name, const void *inData, int size, RamFile::Flags flags)
: m_size(size)
, m_flags(flags)
{
auto guard = qScopeGuard([this] {
cleanup();
});
#if HAVE_MEMFD
m_fd = FileDescriptor(memfd_create(name, MFD_CLOEXEC | MFD_ALLOW_SEALING));
if (!m_fd.isValid()) {
qCWarning(KWIN_CORE).nospace() << name << ": Can't create memfd: " << strerror(errno);
return;
}
if (ftruncate(m_fd.get(), size) < 0) {
qCWarning(KWIN_CORE).nospace() << name << ": Failed to ftruncate memfd: " << strerror(errno);
return;
}
void *data = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd.get(), 0);
if (data == MAP_FAILED) {
qCWarning(KWIN_CORE).nospace() << name << ": mmap failed: " << strerror(errno);
return;
}
#else
m_tmp = std::make_unique<QTemporaryFile>();
if (!m_tmp->open()) {
qCWarning(KWIN_CORE).nospace() << name << ": Can't open temporary file";
return;
}
if (unlink(m_tmp->fileName().toUtf8().constData()) != 0) {
qCWarning(KWIN_CORE).nospace() << name << ": Failed to remove temporary file from filesystem: " << strerror(errno);
}
if (!m_tmp->resize(size)) {
qCWarning(KWIN_CORE).nospace() << name << ": Failed to resize temporary file";
return;
}
uchar *data = m_tmp->map(0, size);
if (!data) {
qCWarning(KWIN_CORE).nospace() << name << ": map failed";
return;
}
#endif
memcpy(data, inData, size);
#if HAVE_MEMFD
munmap(data, size);
#else
m_tmp->unmap(data);
#endif
int seals = F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_SEAL;
if (flags.testFlag(RamFile::Flag::SealWrite)) {
seals |= F_SEAL_WRITE;
}
// This can fail for QTemporaryFile based on the underlying file system.
if (fcntl(fd(), F_ADD_SEALS, seals) != 0) {
qCDebug(KWIN_CORE).nospace() << name << ": Failed to seal RamFile: " << strerror(errno);
}
guard.dismiss();
}
RamFile::RamFile(RamFile &&other) Q_DECL_NOEXCEPT
: m_size(std::exchange(other.m_size, 0))
, m_flags(std::exchange(other.m_flags, RamFile::Flags{}))
#if HAVE_MEMFD
, m_fd(std::exchange(other.m_fd, KWin::FileDescriptor{}))
#else
, m_tmp(std::exchange(other.m_tmp, {}))
#endif
{
}
RamFile &RamFile::operator=(RamFile &&other) Q_DECL_NOEXCEPT
{
cleanup();
m_size = std::exchange(other.m_size, 0);
m_flags = std::exchange(other.m_flags, RamFile::Flags{});
#if HAVE_MEMFD
m_fd = std::exchange(other.m_fd, KWin::FileDescriptor{});
#else
m_tmp = std::exchange(other.m_tmp, {});
#endif
return *this;
}
RamFile::~RamFile()
{
cleanup();
}
void RamFile::cleanup()
{
#if HAVE_MEMFD
m_fd = KWin::FileDescriptor();
#else
m_tmp.reset();
#endif
}
bool RamFile::isValid() const
{
return fd() != -1;
}
RamFile::Flags RamFile::effectiveFlags() const
{
Flags flags = {};
const int seals = fcntl(fd(), F_GET_SEALS);
if (seals > 0) {
if (seals & F_SEAL_WRITE) {
flags.setFlag(Flag::SealWrite);
}
}
return flags;
}
int RamFile::fd() const
{
#if HAVE_MEMFD
return m_fd.get();
#else
return m_tmp->handle();
#endif
}
int RamFile::size() const
{
return m_size;
}
} // namespace KWin
@@ -0,0 +1,118 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2022 MBition GmbH
SPDX-FileContributor: Kai Uwe Broulik <kai_uwe.broulik@mbition.io>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "config-kwin.h"
#include <kwin_export.h>
#if HAVE_MEMFD
#include "filedescriptor.h"
#else
#include <QTemporaryFile>
#include <memory>
#endif
#include <QFlag>
class QByteArray;
namespace KWin
{
/**
* @brief Creates a file in memory.
*
* This is useful for passing larger data to clients,
* for example the xkeymap.
*
* If memfd is supported, it is used, otherwise
* a temporary file is created.
*
* @note It is advisable not to send the same file
* descriptor out to multiple clients unless it
* is sealed for writing. Check which flags actually
* apply before handing out the file descriptor.
*
* @sa effectiveFlags()
*/
class KWIN_EXPORT RamFile
{
public:
/**
* Flags to use when creating the file.
*
* @note Check with effectiveFlags() which flags
* actually apply after the file was created.
*/
enum class Flag {
SealWrite = 1 << 0, ///< Seal the file descriptor for writing.
};
Q_DECLARE_FLAGS(Flags, Flag)
RamFile() = default;
/**
* Create a file of given size with given data.
*
* @note You should call seal() after copying the data into the file.
*
* @param name The file name, useful for debugging.
* @param data The data to store in the file.
* @param size The size of the file.
* @param flags The flags to use when creating the file.
*/
RamFile(const char *name, const void *inData, int size, Flags flags = {});
RamFile(RamFile &&other) Q_DECL_NOEXCEPT;
RamFile &operator=(RamFile &&other) Q_DECL_NOEXCEPT;
/**
* Destroys the file.
*/
~RamFile();
/**
* Whether this instance contains a valid file descriptor.
*/
bool isValid() const;
/**
* The flags that are effectively applied.
*
* For instance, even though SealWrite was passed in the constructor,
* it might not be supported.
*/
Flags effectiveFlags() const;
/**
* The underlying file descriptor
*
* @return The fd, or -1 if there is none.
*/
int fd() const;
/**
* The size of the file
*/
int size() const;
private:
void cleanup();
int m_size = 0;
Flags m_flags = {};
#if HAVE_MEMFD
KWin::FileDescriptor m_fd;
#else
std::unique_ptr<QTemporaryFile> m_tmp;
#endif
};
} // namespace KWin
@@ -0,0 +1,27 @@
/*
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "realtime.h"
#include "config-kwin.h"
#include <pthread.h>
#include <sched.h>
namespace KWin
{
void gainRealTime()
{
#if HAVE_SCHED_RESET_ON_FORK
const int minPriority = sched_get_priority_min(SCHED_RR);
sched_param sp;
sp.sched_priority = minPriority;
pthread_setschedparam(pthread_self(), SCHED_RR | SCHED_RESET_ON_FORK, &sp);
#endif
}
} // namespace KWin
@@ -0,0 +1,19 @@
/*
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kwin_export.h"
namespace KWin
{
/**
* Makes the calling thread to use realtime scheduling.
*/
KWIN_EXPORT void gainRealTime();
} // namespace KWin
@@ -0,0 +1,27 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#pragma once
#include <limits>
#include <type_traits>
struct wl_resource;
namespace KWin
{
template<typename T>
T resource_cast(::wl_resource *resource)
{
using ObjectType = std::remove_pointer_t<std::remove_cv_t<T>>;
if (auto resourceContainer = ObjectType::Resource::fromResource(resource)) {
return static_cast<T>(resourceContainer->object());
}
return T();
}
} // namespace KWin
@@ -0,0 +1,74 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2020 Méven Car <meven.car@enioka.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
// kwin
#include "utils/executable_path.h"
// Qt
#include <QFileInfo>
#include <QLoggingCategory>
#include <QProcess>
// KF
#include <KApplicationTrader>
namespace KWin
{
const static QString s_waylandInterfaceName = QStringLiteral("X-KDE-Wayland-Interfaces");
const static QString s_dbusRestrictedInterfaceName = QStringLiteral("X-KDE-DBUS-Restricted-Interfaces");
static QStringList fetchProcessServiceField(const QString &executablePath, const QString &fieldName)
{
// needed to be able to use the logging category in a header static function
static QLoggingCategory KWIN_UTILS("KWIN_UTILS", QtWarningMsg);
const auto servicesFound = KApplicationTrader::query([&executablePath](const KService::Ptr &service) {
const auto splitCommandList = QProcess::splitCommand(service->exec());
if (splitCommandList.isEmpty()) {
return false;
}
return QFileInfo(splitCommandList.first()).canonicalFilePath() == executablePath;
});
if (servicesFound.isEmpty()) {
qCDebug(KWIN_UTILS) << "Could not find the desktop file for" << executablePath;
return {};
}
const auto fieldValues = servicesFound.first()->property<QStringList>(fieldName);
if (KWIN_UTILS().isDebugEnabled()) {
qCDebug(KWIN_UTILS) << "Interfaces found for" << executablePath << fieldName << ":" << fieldValues;
}
return fieldValues;
}
static inline QStringList fetchRequestedInterfacesForDesktopId(const QString &desktopId)
{
const auto service = KService::serviceByDesktopName(desktopId);
if (!service) {
return {};
}
return service->property<QStringList>(s_waylandInterfaceName);
}
static inline QStringList fetchRequestedInterfaces(const QString &executablePath)
{
return fetchProcessServiceField(executablePath, s_waylandInterfaceName);
}
static inline QStringList fetchRestrictedDBusInterfacesFromPid(const uint pid)
{
const auto executablePath = executablePathFromPid(pid);
if (executablePath.isEmpty()) {
return QStringList();
}
return fetchProcessServiceField(executablePath, s_dbusRestrictedInterfaceName);
}
} // namespace
@@ -0,0 +1,36 @@
/*
SPDX-FileCopyrightText: 2024 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "utils/filedescriptor.h"
#include <optional>
#include <sys/socket.h>
namespace KWin
{
struct SocketPair
{
FileDescriptor fds[2];
static std::optional<SocketPair> create(int domain, int type, int protocol)
{
int fds[2];
if (socketpair(domain, type, protocol, fds) < 0) {
return std::nullopt;
}
return SocketPair{
.fds = {
FileDescriptor(fds[0]),
FileDescriptor(fds[1]),
},
};
}
};
} // namespace KWin
@@ -0,0 +1,60 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "utils/softwarevsyncmonitor.h"
namespace KWin
{
std::unique_ptr<SoftwareVsyncMonitor> SoftwareVsyncMonitor::create()
{
return std::unique_ptr<SoftwareVsyncMonitor>{new SoftwareVsyncMonitor()};
}
SoftwareVsyncMonitor::SoftwareVsyncMonitor()
{
connect(&m_softwareClock, &QTimer::timeout, this, &SoftwareVsyncMonitor::handleSyntheticVsync);
m_softwareClock.setSingleShot(true);
}
int SoftwareVsyncMonitor::refreshRate() const
{
return m_refreshRate;
}
void SoftwareVsyncMonitor::setRefreshRate(int refreshRate)
{
m_refreshRate = refreshRate;
}
void SoftwareVsyncMonitor::handleSyntheticVsync()
{
Q_EMIT vblankOccurred(m_vblankTimestamp);
}
template<typename T>
T alignTimestamp(const T &timestamp, const T &alignment)
{
return timestamp + ((alignment - (timestamp % alignment)) % alignment);
}
void SoftwareVsyncMonitor::arm()
{
if (m_softwareClock.isActive()) {
return;
}
const std::chrono::nanoseconds currentTime(std::chrono::steady_clock::now().time_since_epoch());
const std::chrono::nanoseconds vblankInterval(1'000'000'000'000ull / m_refreshRate);
m_vblankTimestamp = alignTimestamp(currentTime, vblankInterval);
m_softwareClock.start(std::chrono::duration_cast<std::chrono::milliseconds>(m_vblankTimestamp - currentTime));
}
} // namespace KWin
#include "moc_softwarevsyncmonitor.cpp"
@@ -0,0 +1,47 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "utils/vsyncmonitor.h"
#include <QTimer>
#include <memory>
namespace KWin
{
/**
* The SoftwareVsyncMonitor class provides synthetic vblank events with constant interval.
*
* The software vsync monitor can never fail and it is always available. It can be used as
* fallback if hardware based approaches to monitor vsync events are unavailable.
*
* The vblank interval can be changed by calling the setRefreshRate() function.
*/
class KWIN_EXPORT SoftwareVsyncMonitor : public VsyncMonitor
{
Q_OBJECT
public:
static std::unique_ptr<SoftwareVsyncMonitor> create();
int refreshRate() const;
void setRefreshRate(int refreshRate);
public Q_SLOTS:
void arm() override;
private:
explicit SoftwareVsyncMonitor();
void handleSyntheticVsync();
QTimer m_softwareClock;
int m_refreshRate = 60000;
std::chrono::nanoseconds m_vblankTimestamp = std::chrono::nanoseconds::zero();
};
} // namespace KWin
@@ -0,0 +1,87 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "subsurfacemonitor.h"
#include "wayland/subcompositor.h"
#include "wayland/surface.h"
namespace KWin
{
SubSurfaceMonitor::SubSurfaceMonitor(SurfaceInterface *surface, QObject *parent)
: QObject(parent)
{
registerSurface(surface);
}
void SubSurfaceMonitor::registerSubSurface(SubSurfaceInterface *subSurface)
{
SurfaceInterface *surface = subSurface->surface();
connect(subSurface, &SubSurfaceInterface::positionChanged,
this, &SubSurfaceMonitor::subSurfaceMoved);
connect(surface, &SurfaceInterface::sizeChanged,
this, &SubSurfaceMonitor::subSurfaceResized);
connect(surface, &SurfaceInterface::mapped,
this, &SubSurfaceMonitor::subSurfaceMapped);
connect(surface, &SurfaceInterface::unmapped,
this, &SubSurfaceMonitor::subSurfaceUnmapped);
connect(surface, &SurfaceInterface::bufferSizeChanged,
this, &SubSurfaceMonitor::subSurfaceBufferSizeChanged);
connect(surface, &SurfaceInterface::committed,
this, [this, subSurface]() {
Q_EMIT subSurfaceCommitted(subSurface);
});
registerSurface(surface);
}
void SubSurfaceMonitor::unregisterSubSurface(SubSurfaceInterface *subSurface)
{
SurfaceInterface *surface = subSurface->surface();
if (!surface) {
return;
}
disconnect(subSurface, nullptr, this, nullptr);
unregisterSurface(surface);
}
void SubSurfaceMonitor::registerSurface(SurfaceInterface *surface)
{
connect(surface, &SurfaceInterface::childSubSurfaceAdded,
this, &SubSurfaceMonitor::subSurfaceAdded);
connect(surface, &SurfaceInterface::childSubSurfaceRemoved,
this, &SubSurfaceMonitor::subSurfaceRemoved);
connect(surface, &SurfaceInterface::childSubSurfaceAdded,
this, &SubSurfaceMonitor::registerSubSurface);
connect(surface, &SurfaceInterface::childSubSurfaceRemoved,
this, &SubSurfaceMonitor::unregisterSubSurface);
const QList<SubSurfaceInterface *> below = surface->below();
for (SubSurfaceInterface *childSubSurface : below) {
registerSubSurface(childSubSurface);
}
const QList<SubSurfaceInterface *> above = surface->above();
for (SubSurfaceInterface *childSubSurface : above) {
registerSubSurface(childSubSurface);
}
}
void SubSurfaceMonitor::unregisterSurface(SurfaceInterface *surface)
{
disconnect(surface, nullptr, this, nullptr);
}
} // namespace KWin
#include "moc_subsurfacemonitor.cpp"
@@ -0,0 +1,75 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QObject>
namespace KWin
{
class SurfaceInterface;
class SubSurfaceInterface;
}
namespace KWin
{
/**
* The SubSurfaceMonitor class provides a convenient way for monitoring changes in
* sub-surface trees, e.g. addition or removal of sub-surfaces, etc.
*/
class SubSurfaceMonitor : public QObject
{
Q_OBJECT
public:
/**
* Constructs a SubSurfaceTreeMonitor with the given @a surface and @a parent.
*/
SubSurfaceMonitor(SurfaceInterface *surface, QObject *parent);
Q_SIGNALS:
/**
* This signal is emitted when a new sub-surface has been added to the tree.
*/
void subSurfaceAdded();
/**
* This signal is emitted when a sub-surface has been removed from the tree.
*/
void subSurfaceRemoved();
/**
* This signal is emitted when a sub-surface has been moved relative to its parent.
*/
void subSurfaceMoved();
/**
* This signal is emitted when a sub-surface has been resized.
*/
void subSurfaceResized();
/**
* This signal is emitted when a sub-surface is mapped.
*/
void subSurfaceMapped();
/**
* This signal is emitted when a sub-surface is unmapped.
*/
void subSurfaceUnmapped();
/**
* This signal is emitted when the buffer size of a subsurface has changed.
*/
void subSurfaceBufferSizeChanged();
void subSurfaceCommitted(SubSurfaceInterface *subSurface);
private:
void registerSubSurface(SubSurfaceInterface *subSurface);
void unregisterSubSurface(SubSurfaceInterface *subSurface);
void registerSurface(SurfaceInterface *surface);
void unregisterSurface(SurfaceInterface *surface);
};
} // namespace KWin
@@ -0,0 +1,147 @@
/*
SPDX-FileCopyrightText: 2024 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "utils/svgcursorreader.h"
#include "utils/common.h"
#include <QDir>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QPainter>
#include <QSvgRenderer>
namespace KWin
{
struct SvgCursorMetaDataEntry
{
static std::optional<SvgCursorMetaDataEntry> parse(const QJsonObject &object);
QString fileName;
qreal nominalSize;
QPointF hotspot;
std::chrono::milliseconds delay;
};
std::optional<SvgCursorMetaDataEntry> SvgCursorMetaDataEntry::parse(const QJsonObject &object)
{
const QJsonValue fileName = object.value(QLatin1String("filename"));
if (!fileName.isString()) {
return std::nullopt;
}
const QJsonValue nominalSize = object.value(QLatin1String("nominal_size"));
if (!nominalSize.isDouble()) {
return std::nullopt;
}
const QJsonValue hotspotX = object.value(QLatin1String("hotspot_x"));
if (!hotspotX.isDouble()) {
return std::nullopt;
}
const QJsonValue hotspotY = object.value(QLatin1String("hotspot_y"));
if (!hotspotY.isDouble()) {
return std::nullopt;
}
const QJsonValue delay = object.value(QLatin1String("delay"));
return SvgCursorMetaDataEntry{
.fileName = fileName.toString(),
.nominalSize = nominalSize.toDouble(),
.hotspot = QPointF(hotspotX.toDouble(), hotspotY.toDouble()),
.delay = std::chrono::milliseconds(delay.toInt()),
};
}
struct SvgCursorMetaData
{
static std::optional<SvgCursorMetaData> parse(const QString &filePath);
QList<SvgCursorMetaDataEntry> entries;
};
std::optional<SvgCursorMetaData> SvgCursorMetaData::parse(const QString &filePath)
{
QFile metaDataFile(filePath);
if (!metaDataFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
return std::nullopt;
}
QJsonParseError jsonParseError;
const QJsonDocument document = QJsonDocument::fromJson(metaDataFile.readAll(), &jsonParseError);
if (jsonParseError.error) {
return std::nullopt;
}
if (!document.isArray()) {
return std::nullopt;
}
const QJsonArray array = document.array();
if (array.isEmpty()) {
return std::nullopt;
}
QList<SvgCursorMetaDataEntry> entries;
for (int i = 0; i < array.size(); ++i) {
const QJsonValue element = array.at(i);
if (!element.isObject()) {
return std::nullopt;
}
if (const auto entry = SvgCursorMetaDataEntry::parse(element.toObject())) {
entries.append(entry.value());
} else {
return std::nullopt;
}
}
return SvgCursorMetaData{
.entries = entries,
};
}
QList<CursorSprite> SvgCursorReader::load(const QString &containerPath, int desiredSize, qreal devicePixelRatio)
{
const QDir containerDir(containerPath);
const QString metadataFilePath = containerDir.filePath(QStringLiteral("metadata.json"));
const auto metadata = SvgCursorMetaData::parse(metadataFilePath);
if (!metadata.has_value()) {
qCWarning(KWIN_CORE) << "Failed to parse" << metadataFilePath;
return {};
}
QList<CursorSprite> sprites;
for (const SvgCursorMetaDataEntry &entry : metadata->entries) {
const QString filePath = containerDir.filePath(entry.fileName);
const qreal scale = desiredSize / entry.nominalSize;
QSvgRenderer renderer(filePath);
if (!renderer.isValid()) {
qCWarning(KWIN_CORE) << "Failed to render" << filePath;
return {};
}
const QRect bounds(QPoint(0, 0), renderer.defaultSize() * scale);
QImage image(bounds.size() * devicePixelRatio, QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::transparent);
image.setDevicePixelRatio(devicePixelRatio);
QPainter painter(&image);
renderer.render(&painter, bounds);
painter.end();
sprites.append(CursorSprite(image, entry.hotspot * scale, entry.delay));
}
return sprites;
}
} // namespace KWin
@@ -0,0 +1,20 @@
/*
SPDX-FileCopyrightText: 2024 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "utils/cursortheme.h"
namespace KWin
{
class SvgCursorReader
{
public:
static QList<CursorSprite> load(const QString &filePath, int desiredSize, qreal devicePixelRatio);
};
} // namespace KWin
@@ -0,0 +1,295 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "udev.h"
#include "utils/common.h"
// Qt
#include <QByteArray>
#include <QDebug>
// system
#include <cerrno>
#include <functional>
#include <libudev.h>
namespace KWin
{
Udev::Udev()
: m_udev(udev_new())
{
}
Udev::~Udev()
{
if (m_udev) {
udev_unref(m_udev);
}
}
class UdevEnumerate
{
public:
UdevEnumerate(Udev *udev);
~UdevEnumerate();
enum class Match {
SubSystem,
SysName
};
void addMatch(Match match, const char *name);
void scan();
std::vector<std::unique_ptr<UdevDevice>> find();
private:
Udev *m_udev;
struct EnumerateDeleter
{
void operator()(udev_enumerate *e)
{
udev_enumerate_unref(e);
}
};
std::unique_ptr<udev_enumerate, EnumerateDeleter> m_enumerate;
};
UdevEnumerate::UdevEnumerate(Udev *udev)
: m_udev(udev)
, m_enumerate(udev_enumerate_new(*m_udev))
{
}
UdevEnumerate::~UdevEnumerate() = default;
void UdevEnumerate::addMatch(UdevEnumerate::Match match, const char *name)
{
if (!m_enumerate) {
return;
}
switch (match) {
case Match::SubSystem:
udev_enumerate_add_match_subsystem(m_enumerate.get(), name);
break;
case Match::SysName:
udev_enumerate_add_match_sysname(m_enumerate.get(), name);
break;
default:
Q_UNREACHABLE();
break;
}
}
void UdevEnumerate::scan()
{
if (!m_enumerate) {
return;
}
udev_enumerate_scan_devices(m_enumerate.get());
}
std::vector<std::unique_ptr<UdevDevice>> UdevEnumerate::find()
{
if (!m_enumerate) {
return {};
}
std::vector<std::unique_ptr<UdevDevice>> vect;
udev_list_entry *it = udev_enumerate_get_list_entry(m_enumerate.get());
while (it) {
auto current = it;
it = udev_list_entry_get_next(it);
auto device = m_udev->deviceFromSyspath(udev_list_entry_get_name(current));
if (device) {
vect.push_back(std::move(device));
}
}
return vect;
}
std::vector<std::unique_ptr<UdevDevice>> Udev::listGPUs()
{
if (!m_udev) {
return {};
}
#if defined(Q_OS_FREEBSD)
std::vector<std::unique_ptr<UdevDevice>> r;
r.push_back(deviceFromSyspath("/dev/dri/card0"));
return r;
#else
UdevEnumerate enumerate(this);
enumerate.addMatch(UdevEnumerate::Match::SubSystem, "drm");
enumerate.addMatch(UdevEnumerate::Match::SysName, "card[0-9]");
enumerate.scan();
auto vect = enumerate.find();
std::sort(vect.begin(), vect.end(), [](const auto &device1, const auto &device2) {
// prevent usb devices from becoming the primaryGpu
if (device1->isHotpluggable()) {
return false;
}
if (device2->isHotpluggable()) {
return true;
}
// if set as boot GPU, prefer 1
if (device1->isBootVga()) {
return true;
}
// if set as boot GPU, prefer 2
if (device2->isBootVga()) {
return false;
}
return false;
});
return vect;
#endif
}
std::unique_ptr<UdevDevice> Udev::deviceFromSyspath(const char *syspath)
{
auto dev = udev_device_new_from_syspath(m_udev, syspath);
if (!dev) {
qCWarning(KWIN_CORE) << "failed to retrieve device for" << syspath << strerror(errno);
return {};
}
return std::make_unique<UdevDevice>(dev);
}
std::unique_ptr<UdevMonitor> Udev::monitor()
{
auto m = std::make_unique<UdevMonitor>(this);
if (m->isValid()) {
return m;
} else {
return nullptr;
}
}
UdevDevice::UdevDevice(udev_device *device)
: m_device(device)
{
Q_ASSERT(device);
}
UdevDevice::~UdevDevice()
{
udev_device_unref(m_device);
}
QString UdevDevice::devNode() const
{
return QString::fromUtf8(udev_device_get_devnode(m_device));
}
dev_t UdevDevice::devNum() const
{
return udev_device_get_devnum(m_device);
}
const char *UdevDevice::property(const char *key)
{
return udev_device_get_property_value(m_device, key);
}
QMap<QByteArray, QByteArray> UdevDevice::properties() const
{
QMap<QByteArray, QByteArray> r;
auto it = udev_device_get_properties_list_entry(m_device);
auto current = it;
udev_list_entry_foreach(current, it)
{
r.insert(udev_list_entry_get_name(current), udev_list_entry_get_value(current));
}
return r;
}
bool UdevDevice::hasProperty(const char *key, const char *value)
{
const char *p = property(key);
if (!p) {
return false;
}
return qstrcmp(p, value) == 0;
}
bool UdevDevice::isBootVga() const
{
auto pci = udev_device_get_parent_with_subsystem_devtype(m_device, "pci", nullptr);
if (!pci) {
return false;
}
const char *systAttrValue = udev_device_get_sysattr_value(pci, "boot_vga");
return systAttrValue && qstrcmp(systAttrValue, "1") == 0;
}
QString UdevDevice::seat() const
{
QString deviceSeat = udev_device_get_property_value(m_device, "ID_SEAT");
if (deviceSeat.isEmpty()) {
deviceSeat = QStringLiteral("seat0");
}
return deviceSeat;
}
QString UdevDevice::action() const
{
return QString::fromLocal8Bit(udev_device_get_action(m_device));
}
bool UdevDevice::isHotpluggable() const
{
const auto devPath = QString::fromUtf8(udev_device_get_devpath(m_device));
return devPath.contains("usb", Qt::CaseInsensitive);
}
UdevMonitor::UdevMonitor(Udev *udev)
: m_monitor(udev_monitor_new_from_netlink(*udev, "udev"))
{
}
UdevMonitor::~UdevMonitor()
{
if (m_monitor) {
udev_monitor_unref(m_monitor);
}
}
int UdevMonitor::fd() const
{
if (m_monitor) {
return udev_monitor_get_fd(m_monitor);
}
return -1;
}
void UdevMonitor::filterSubsystemDevType(const char *subSystem, const char *devType)
{
if (!m_monitor) {
return;
}
udev_monitor_filter_add_match_subsystem_devtype(m_monitor, subSystem, devType);
}
void UdevMonitor::enable()
{
if (!m_monitor) {
return;
}
udev_monitor_enable_receiving(m_monitor);
}
std::unique_ptr<UdevDevice> UdevMonitor::getDevice()
{
if (!m_monitor) {
return nullptr;
}
auto dev = udev_monitor_receive_device(m_monitor);
return dev ? std::make_unique<UdevDevice>(dev) : nullptr;
}
}
@@ -0,0 +1,101 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <kwin_export.h>
#include <memory>
#include <QList>
#include <vector>
#include <sys/types.h>
struct udev;
struct udev_device;
struct udev_monitor;
namespace KWin
{
class Udev;
class KWIN_EXPORT UdevDevice
{
public:
UdevDevice(udev_device *device);
~UdevDevice();
QString devNode() const;
dev_t devNum() const;
const char *property(const char *key);
bool hasProperty(const char *key, const char *value);
QString action() const;
QMap<QByteArray, QByteArray> properties() const;
bool isBootVga() const;
QString seat() const;
bool isHotpluggable() const;
operator udev_device *() const
{
return m_device;
}
operator udev_device *()
{
return m_device;
}
private:
udev_device *const m_device;
};
class KWIN_EXPORT UdevMonitor
{
public:
explicit UdevMonitor(Udev *udev);
~UdevMonitor();
int fd() const;
bool isValid() const
{
return m_monitor != nullptr;
}
void filterSubsystemDevType(const char *subSystem, const char *devType = nullptr);
void enable();
std::unique_ptr<UdevDevice> getDevice();
private:
udev_monitor *m_monitor;
};
class KWIN_EXPORT Udev
{
public:
Udev();
~Udev();
bool isValid() const
{
return m_udev != nullptr;
}
std::vector<std::unique_ptr<UdevDevice>> listGPUs();
std::unique_ptr<UdevDevice> deviceFromSyspath(const char *syspath);
std::unique_ptr<UdevMonitor> monitor();
operator udev *() const
{
return m_udev;
}
operator udev *()
{
return m_udev;
}
private:
struct udev *m_udev;
};
}
@@ -0,0 +1,87 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "version.h"
#include <QChar>
#include <QList>
namespace KWin
{
Version::Version(uint32_t major, uint32_t minor, uint32_t patch)
: m_major(major)
, m_minor(minor)
, m_patch(patch)
{
}
bool Version::isValid() const
{
return m_major > 0 || m_minor > 0 || m_patch > 0;
}
uint32_t Version::majorVersion() const
{
return m_major;
}
uint32_t Version::minorVersion() const
{
return m_minor;
}
uint32_t Version::patchVersion() const
{
return m_patch;
}
Version Version::parseString(QByteArrayView versionString)
{
// Skip any leading non digit
int start = 0;
while (start < versionString.length() && !QChar::fromLatin1(versionString[start]).isDigit()) {
start++;
}
// Strip any non digit, non '.' characters from the end
int end = start;
while (end < versionString.length() && (versionString[end] == '.' || QChar::fromLatin1(versionString[end]).isDigit())) {
end++;
}
const QByteArray result = versionString.toByteArray().mid(start, end - start);
const QList<QByteArray> tokens = result.split('.');
if (tokens.empty()) {
return Version(0, 0, 0);
}
const uint64_t major = tokens.at(0).toInt();
const uint64_t minor = tokens.count() > 1 ? tokens.at(1).toInt() : 0;
const uint64_t patch = tokens.count() > 2 ? tokens.at(2).toInt() : 0;
return Version(major, minor, patch);
}
QString Version::toString() const
{
if (m_patch == 0) {
return QString::number(m_major) + '.' + QString::number(m_minor);
} else {
return QString::number(m_major) + '.' + QString::number(m_minor) + '.' + QString::number(m_patch);
}
}
QByteArray Version::toByteArray() const
{
if (m_patch == 0) {
return QByteArray::number(m_major) + '.' + QByteArray::number(m_minor);
} else {
return QByteArray::number(m_major) + '.' + QByteArray::number(m_minor) + '.' + QByteArray::number(m_patch);
}
}
}
@@ -0,0 +1,41 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kwin_export.h"
#include <QByteArray>
#include <QString>
namespace KWin
{
class KWIN_EXPORT Version
{
public:
Version(uint32_t major, uint32_t minor, uint32_t patch = 0);
Version() = default;
auto operator<=>(const Version &other) const = default;
bool isValid() const;
uint32_t majorVersion() const;
uint32_t minorVersion() const;
uint32_t patchVersion() const;
QString toString() const;
QByteArray toByteArray() const;
static Version parseString(QByteArrayView versionString);
private:
uint32_t m_major = 0;
uint32_t m_minor = 0;
uint32_t m_patch = 0;
};
}
@@ -0,0 +1,16 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "utils/vsyncmonitor.h"
namespace KWin
{
VsyncMonitor::VsyncMonitor() = default;
} // namespace KWin
#include "moc_vsyncmonitor.cpp"
@@ -0,0 +1,36 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "effect/globals.h"
#include <QObject>
#include <chrono>
namespace KWin
{
/**
* The VsyncMonitor class provides a convenient way to monitor vblank events.
*/
class KWIN_EXPORT VsyncMonitor : public QObject
{
Q_OBJECT
public:
explicit VsyncMonitor();
public Q_SLOTS:
virtual void arm() = 0;
Q_SIGNALS:
void errorOccurred();
void vblankOccurred(std::chrono::nanoseconds timestamp);
};
} // namespace KWin
@@ -0,0 +1,676 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2006 Lubos Lunak <l.lunak@kde.org>
SPDX-FileCopyrightText: 2012 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "utils/xcbutils.h"
#include "utils/common.h"
#include <core/output.h>
#include <workspace.h>
// Qt
#include <QDebug>
// xcb
#include <cmath>
#include <xcb/composite.h>
#include <xcb/damage.h>
#include <xcb/glx.h>
#include <xcb/randr.h>
#include <xcb/render.h>
#include <xcb/shape.h>
#include <xcb/sync.h>
#include <xcb/xfixes.h>
// system
#include <sys/shm.h>
#include <sys/types.h>
namespace KWin
{
namespace Xcb
{
static const int COMPOSITE_MAX_MAJOR = 0;
static const int COMPOSITE_MAX_MINOR = 4;
static const int DAMAGE_MAX_MAJOR = 1;
static const int DAMAGE_MIN_MAJOR = 1;
static const int SYNC_MAX_MAJOR = 3;
static const int SYNC_MAX_MINOR = 0;
static const int RANDR_MAX_MAJOR = 1;
static const int RANDR_MAX_MINOR = 4;
static const int RENDER_MAX_MAJOR = 0;
static const int RENDER_MAX_MINOR = 11;
static const int XFIXES_MAX_MAJOR = 5;
static const int XFIXES_MAX_MINOR = 0;
QList<QByteArray> shapeOpCodes()
{
// see https://www.x.org/releases/X11R7.7/doc/xextproto/shape.html
// extracted from <xcb/shape.h>
return QList<QByteArray>({QByteArrayLiteral("QueryVersion"),
QByteArrayLiteral("Rectangles"),
QByteArrayLiteral("Mask"),
QByteArrayLiteral("Combine"),
QByteArrayLiteral("Offset"),
QByteArrayLiteral("Extents"),
QByteArrayLiteral("Input"),
QByteArrayLiteral("InputSelected"),
QByteArrayLiteral("GetRectangles")});
}
QList<QByteArray> randrOpCodes()
{
// see https://www.x.org/releases/X11R7.7/doc/randrproto/randrproto.txt
// extracted from <xcb/randr.h>
return QList<QByteArray>({QByteArrayLiteral("QueryVersion"),
QByteArray(""), // doesn't exist
QByteArrayLiteral("SetScreenConfig"),
QByteArray(""), // doesn't exits
QByteArrayLiteral("SelectInput"),
QByteArrayLiteral("GetScreenInfo"),
QByteArrayLiteral("GetScreenSizeRange"),
QByteArrayLiteral("SetScreenSize"),
QByteArrayLiteral("GetScreenResources"),
QByteArrayLiteral("GetOutputInfo"),
QByteArrayLiteral("ListOutputProperties"),
QByteArrayLiteral("QueryOutputProperty"),
QByteArrayLiteral("ConfigureOutputProperty"),
QByteArrayLiteral("ChangeOutputProperty"),
QByteArrayLiteral("DeleteOutputProperty"),
QByteArrayLiteral("GetOutputproperty"),
QByteArrayLiteral("CreateMode"),
QByteArrayLiteral("DestroyMode"),
QByteArrayLiteral("AddOutputMode"),
QByteArrayLiteral("DeleteOutputMode"),
QByteArrayLiteral("GetCrtcInfo"),
QByteArrayLiteral("SetCrtcConfig"),
QByteArrayLiteral("GetCrtcGammaSize"),
QByteArrayLiteral("GetCrtcGamma"),
QByteArrayLiteral("SetCrtcGamma"),
QByteArrayLiteral("GetScreenResourcesCurrent"),
QByteArrayLiteral("SetCrtcTransform"),
QByteArrayLiteral("GetCrtcTransform"),
QByteArrayLiteral("GetPanning"),
QByteArrayLiteral("SetPanning"),
QByteArrayLiteral("SetOutputPrimary"),
QByteArrayLiteral("GetOutputPrimary"),
QByteArrayLiteral("GetProviders"),
QByteArrayLiteral("GetProviderInfo"),
QByteArrayLiteral("SetProviderOffloadSink"),
QByteArrayLiteral("SetProviderOutputSource"),
QByteArrayLiteral("ListProviderProperties"),
QByteArrayLiteral("QueryProviderProperty"),
QByteArrayLiteral("ConfigureProviderroperty"),
QByteArrayLiteral("ChangeProviderProperty"),
QByteArrayLiteral("DeleteProviderProperty"),
QByteArrayLiteral("GetProviderProperty")});
}
QList<QByteArray> randrErrorCodes()
{
// see https://www.x.org/releases/X11R7.7/doc/randrproto/randrproto.txt
// extracted from <xcb/randr.h>
return QList<QByteArray>({QByteArrayLiteral("BadOutput"),
QByteArrayLiteral("BadCrtc"),
QByteArrayLiteral("BadMode"),
QByteArrayLiteral("BadProvider")});
}
QList<QByteArray> damageOpCodes()
{
// see https://www.x.org/releases/X11R7.7/doc/damageproto/damageproto.txt
// extracted from <xcb/damage.h>
return QList<QByteArray>({QByteArrayLiteral("QueryVersion"),
QByteArrayLiteral("Create"),
QByteArrayLiteral("Destroy"),
QByteArrayLiteral("Subtract"),
QByteArrayLiteral("Add")});
}
QList<QByteArray> damageErrorCodes()
{
// see https://www.x.org/releases/X11R7.7/doc/damageproto/damageproto.txt
// extracted from <xcb/damage.h>
return QList<QByteArray>({QByteArrayLiteral("BadDamage")});
}
QList<QByteArray> compositeOpCodes()
{
// see https://www.x.org/releases/X11R7.7/doc/compositeproto/compositeproto.txt
// extracted from <xcb/composite.h>
return QList<QByteArray>({QByteArrayLiteral("QueryVersion"),
QByteArrayLiteral("RedirectWindow"),
QByteArrayLiteral("RedirectSubwindows"),
QByteArrayLiteral("UnredirectWindow"),
QByteArrayLiteral("UnredirectSubwindows"),
QByteArrayLiteral("CreateRegionFromBorderClip"),
QByteArrayLiteral("NameWindowPixmap"),
QByteArrayLiteral("GetOverlayWindow"),
QByteArrayLiteral("ReleaseOverlayWindow")});
}
QList<QByteArray> fixesOpCodes()
{
// see https://www.x.org/releases/X11R7.7/doc/fixesproto/fixesproto.txt
// extracted from <xcb/xfixes.h>
return QList<QByteArray>({QByteArrayLiteral("QueryVersion"),
QByteArrayLiteral("ChangeSaveSet"),
QByteArrayLiteral("SelectSelectionInput"),
QByteArrayLiteral("SelectCursorInput"),
QByteArrayLiteral("GetCursorImage"),
QByteArrayLiteral("CreateRegion"),
QByteArrayLiteral("CreateRegionFromBitmap"),
QByteArrayLiteral("CreateRegionFromWindow"),
QByteArrayLiteral("CreateRegionFromGc"),
QByteArrayLiteral("CreateRegionFromPicture"),
QByteArrayLiteral("DestroyRegion"),
QByteArrayLiteral("SetRegion"),
QByteArrayLiteral("CopyRegion"),
QByteArrayLiteral("UnionRegion"),
QByteArrayLiteral("IntersectRegion"),
QByteArrayLiteral("SubtractRegion"),
QByteArrayLiteral("InvertRegion"),
QByteArrayLiteral("TranslateRegion"),
QByteArrayLiteral("RegionExtents"),
QByteArrayLiteral("FetchRegion"),
QByteArrayLiteral("SetGcClipRegion"),
QByteArrayLiteral("SetWindowShapeRegion"),
QByteArrayLiteral("SetPictureClipRegion"),
QByteArrayLiteral("SetCursorName"),
QByteArrayLiteral("GetCursorName"),
QByteArrayLiteral("GetCursorImageAndName"),
QByteArrayLiteral("ChangeCursor"),
QByteArrayLiteral("ChangeCursorByName"),
QByteArrayLiteral("ExpandRegion"),
QByteArrayLiteral("HideCursor"),
QByteArrayLiteral("ShowCursor"),
QByteArrayLiteral("CreatePointerBarrier"),
QByteArrayLiteral("DeletePointerBarrier")});
}
QList<QByteArray> fixesErrorCodes()
{
// see https://www.x.org/releases/X11R7.7/doc/fixesproto/fixesproto.txt
// extracted from <xcb/xfixes.h>
return QList<QByteArray>({QByteArrayLiteral("BadRegion")});
}
QList<QByteArray> renderOpCodes()
{
// see https://www.x.org/releases/X11R7.7/doc/renderproto/renderproto.txt
// extracted from <xcb/render.h>
return QList<QByteArray>({QByteArrayLiteral("QueryVersion"),
QByteArrayLiteral("QueryPictFormats"),
QByteArrayLiteral("QueryPictIndexValues"),
QByteArrayLiteral("CreatePicture"),
QByteArrayLiteral("ChangePicture"),
QByteArrayLiteral("SetPictureClipRectangles"),
QByteArrayLiteral("FreePicture"),
QByteArrayLiteral("Composite"),
QByteArrayLiteral("Trapezoids"),
QByteArrayLiteral("Triangles"),
QByteArrayLiteral("TriStrip"),
QByteArrayLiteral("TriFan"),
QByteArrayLiteral("CreateGlyphSet"),
QByteArrayLiteral("ReferenceGlyphSet"),
QByteArrayLiteral("FreeGlyphSet"),
QByteArrayLiteral("AddGlyphs"),
QByteArrayLiteral("FreeGlyphs"),
QByteArrayLiteral("CompositeGlyphs8"),
QByteArrayLiteral("CompositeGlyphs16"),
QByteArrayLiteral("CompositeGlyphs32"),
QByteArrayLiteral("FillRectangles"),
QByteArrayLiteral("CreateCursor"),
QByteArrayLiteral("SetPictureTransform"),
QByteArrayLiteral("QueryFilters"),
QByteArrayLiteral("SetPictureFilter"),
QByteArrayLiteral("CreateAnimCursor"),
QByteArrayLiteral("AddTraps"),
QByteArrayLiteral("CreateSolidFill"),
QByteArrayLiteral("CreateLinearGradient"),
QByteArrayLiteral("CreateRadialGradient"),
QByteArrayLiteral("CreateConicalGradient")});
}
QList<QByteArray> syncOpCodes()
{
// see https://www.x.org/releases/X11R7.7/doc/xextproto/sync.html
// extracted from <xcb/sync.h>
return QList<QByteArray>({QByteArrayLiteral("Initialize"),
QByteArrayLiteral("ListSystemCounters"),
QByteArrayLiteral("CreateCounter"),
QByteArrayLiteral("DestroyCounter"),
QByteArrayLiteral("QueryCounter"),
QByteArrayLiteral("Await"),
QByteArrayLiteral("ChangeCounter"),
QByteArrayLiteral("SetCounter"),
QByteArrayLiteral("CreateAlarm"),
QByteArrayLiteral("ChangeAlarm"),
QByteArrayLiteral("DestroyAlarm"),
QByteArrayLiteral("QueryAlarm"),
QByteArrayLiteral("SetPriority"),
QByteArrayLiteral("GetPriority"),
QByteArrayLiteral("CreateFence"),
QByteArrayLiteral("TriggerFence"),
QByteArrayLiteral("ResetFence"),
QByteArrayLiteral("DestroyFence"),
QByteArrayLiteral("QueryFence"),
QByteArrayLiteral("AwaitFence")});
}
static QList<QByteArray> glxOpCodes()
{
return QList<QByteArray>{
QByteArrayLiteral(""),
QByteArrayLiteral("Render"),
QByteArrayLiteral("RenderLarge"),
QByteArrayLiteral("CreateContext"),
QByteArrayLiteral("DestroyContext"),
QByteArrayLiteral("MakeCurrent"),
QByteArrayLiteral("IsDirect"),
QByteArrayLiteral("QueryVersion"),
QByteArrayLiteral("WaitGL"),
QByteArrayLiteral("WaitX"),
QByteArrayLiteral("CopyContext"),
QByteArrayLiteral("SwapBuffers"),
QByteArrayLiteral("UseXFont"),
QByteArrayLiteral("CreateGLXPixmap"),
QByteArrayLiteral("GetVisualConfigs"),
QByteArrayLiteral("DestroyGLXPixmap"),
QByteArrayLiteral("VendorPrivate"),
QByteArrayLiteral("VendorPrivateWithReply"),
QByteArrayLiteral("QueryExtensionsString"),
QByteArrayLiteral("QueryServerString"),
QByteArrayLiteral("ClientInfo"),
QByteArrayLiteral("GetFBConfigs"),
QByteArrayLiteral("CreatePixmap"),
QByteArrayLiteral("DestroyPixmap"),
QByteArrayLiteral("CreateNewContext"),
QByteArrayLiteral("QueryContext"),
QByteArrayLiteral("MakeContextCurrent"),
QByteArrayLiteral("CreatePbuffer"),
QByteArrayLiteral("DestroyPbuffer"),
QByteArrayLiteral("GetDrawableAttributes"),
QByteArrayLiteral("ChangeDrawableAttributes"),
QByteArrayLiteral("CreateWindow"),
QByteArrayLiteral("DeleteWindow"),
QByteArrayLiteral("SetClientInfoARB"),
QByteArrayLiteral("CreateContextAttribsARB"),
QByteArrayLiteral("SetClientInfo2ARB")
// Opcodes 36-100 are unused
// The GL single commands begin at opcode 101
};
}
static QList<QByteArray> glxErrorCodes()
{
return QList<QByteArray>{
QByteArrayLiteral("BadContext"),
QByteArrayLiteral("BadContextState"),
QByteArrayLiteral("BadDrawable"),
QByteArrayLiteral("BadPixmap"),
QByteArrayLiteral("BadContextTag"),
QByteArrayLiteral("BadCurrentWindow"),
QByteArrayLiteral("BadRenderRequest"),
QByteArrayLiteral("BadLargeRequest"),
QByteArrayLiteral("UnsupportedPrivateRequest"),
QByteArrayLiteral("BadFBConfig"),
QByteArrayLiteral("BadPbuffer"),
QByteArrayLiteral("BadCurrentDrawable"),
QByteArrayLiteral("BadWindow"),
QByteArrayLiteral("GLXBadProfileARB")};
}
ExtensionData::ExtensionData()
: version(0)
, eventBase(0)
, errorBase(0)
, majorOpcode(0)
, present(0)
{
}
template<typename reply, typename T, typename F>
void Extensions::initVersion(T cookie, F f, ExtensionData *dataToFill)
{
UniqueCPtr<reply> version(f(connection(), cookie, nullptr));
dataToFill->version = version->major_version * 0x10 + version->minor_version;
}
Extensions *Extensions::s_self = nullptr;
Extensions *Extensions::self()
{
if (!s_self) {
s_self = new Extensions();
}
return s_self;
}
void Extensions::destroy()
{
delete s_self;
s_self = nullptr;
}
Extensions::Extensions()
{
init();
}
Extensions::~Extensions()
{
}
void Extensions::init()
{
xcb_connection_t *c = connection();
Q_ASSERT(c);
xcb_prefetch_extension_data(c, &xcb_shape_id);
xcb_prefetch_extension_data(c, &xcb_randr_id);
xcb_prefetch_extension_data(c, &xcb_damage_id);
xcb_prefetch_extension_data(c, &xcb_composite_id);
xcb_prefetch_extension_data(c, &xcb_xfixes_id);
xcb_prefetch_extension_data(c, &xcb_render_id);
xcb_prefetch_extension_data(c, &xcb_sync_id);
xcb_prefetch_extension_data(c, &xcb_glx_id);
m_shape.name = QByteArray("SHAPE");
m_randr.name = QByteArray("RANDR");
m_damage.name = QByteArray("DAMAGE");
m_composite.name = QByteArray("Composite");
m_fixes.name = QByteArray("XFIXES");
m_render.name = QByteArray("RENDER");
m_sync.name = QByteArray("SYNC");
m_glx.name = QByteArray("GLX");
m_shape.opCodes = shapeOpCodes();
m_randr.opCodes = randrOpCodes();
m_damage.opCodes = damageOpCodes();
m_composite.opCodes = compositeOpCodes();
m_fixes.opCodes = fixesOpCodes();
m_render.opCodes = renderOpCodes();
m_sync.opCodes = syncOpCodes();
m_glx.opCodes = glxOpCodes();
m_randr.errorCodes = randrErrorCodes();
m_damage.errorCodes = damageErrorCodes();
m_fixes.errorCodes = fixesErrorCodes();
m_glx.errorCodes = glxErrorCodes();
extensionQueryReply(xcb_get_extension_data(c, &xcb_shape_id), &m_shape);
extensionQueryReply(xcb_get_extension_data(c, &xcb_randr_id), &m_randr);
extensionQueryReply(xcb_get_extension_data(c, &xcb_damage_id), &m_damage);
extensionQueryReply(xcb_get_extension_data(c, &xcb_composite_id), &m_composite);
extensionQueryReply(xcb_get_extension_data(c, &xcb_xfixes_id), &m_fixes);
extensionQueryReply(xcb_get_extension_data(c, &xcb_render_id), &m_render);
extensionQueryReply(xcb_get_extension_data(c, &xcb_sync_id), &m_sync);
extensionQueryReply(xcb_get_extension_data(c, &xcb_glx_id), &m_glx);
// extension specific queries
xcb_shape_query_version_cookie_t shapeVersion;
xcb_randr_query_version_cookie_t randrVersion;
xcb_damage_query_version_cookie_t damageVersion;
xcb_composite_query_version_cookie_t compositeVersion;
xcb_xfixes_query_version_cookie_t xfixesVersion;
xcb_render_query_version_cookie_t renderVersion;
xcb_sync_initialize_cookie_t syncVersion;
if (m_shape.present) {
shapeVersion = xcb_shape_query_version_unchecked(c);
}
if (m_randr.present) {
randrVersion = xcb_randr_query_version_unchecked(c, RANDR_MAX_MAJOR, RANDR_MAX_MINOR);
xcb_randr_select_input(connection(), rootWindow(), XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE);
}
if (m_damage.present) {
damageVersion = xcb_damage_query_version_unchecked(c, DAMAGE_MAX_MAJOR, DAMAGE_MIN_MAJOR);
}
if (m_composite.present) {
compositeVersion = xcb_composite_query_version_unchecked(c, COMPOSITE_MAX_MAJOR, COMPOSITE_MAX_MINOR);
}
if (m_fixes.present) {
xfixesVersion = xcb_xfixes_query_version_unchecked(c, XFIXES_MAX_MAJOR, XFIXES_MAX_MINOR);
}
if (m_render.present) {
renderVersion = xcb_render_query_version_unchecked(c, RENDER_MAX_MAJOR, RENDER_MAX_MINOR);
}
if (m_sync.present) {
syncVersion = xcb_sync_initialize(c, SYNC_MAX_MAJOR, SYNC_MAX_MINOR);
}
// handle replies
if (m_shape.present) {
initVersion<xcb_shape_query_version_reply_t>(shapeVersion, &xcb_shape_query_version_reply, &m_shape);
}
if (m_randr.present) {
initVersion<xcb_randr_query_version_reply_t>(randrVersion, &xcb_randr_query_version_reply, &m_randr);
}
if (m_damage.present) {
initVersion<xcb_damage_query_version_reply_t>(damageVersion, &xcb_damage_query_version_reply, &m_damage);
}
if (m_composite.present) {
initVersion<xcb_composite_query_version_reply_t>(compositeVersion, &xcb_composite_query_version_reply, &m_composite);
}
if (m_fixes.present) {
initVersion<xcb_xfixes_query_version_reply_t>(xfixesVersion, &xcb_xfixes_query_version_reply, &m_fixes);
}
if (m_render.present) {
initVersion<xcb_render_query_version_reply_t>(renderVersion, &xcb_render_query_version_reply, &m_render);
}
if (m_sync.present) {
initVersion<xcb_sync_initialize_reply_t>(syncVersion, &xcb_sync_initialize_reply, &m_sync);
}
qCDebug(KWIN_CORE) << "Extensions: shape: 0x" << QString::number(m_shape.version, 16)
<< " composite: 0x" << QString::number(m_composite.version, 16)
<< " render: 0x" << QString::number(m_render.version, 16)
<< " fixes: 0x" << QString::number(m_fixes.version, 16)
<< " randr: 0x" << QString::number(m_randr.version, 16)
<< " sync: 0x" << QString::number(m_sync.version, 16)
<< " damage: 0x " << QString::number(m_damage.version, 16);
}
void Extensions::extensionQueryReply(const xcb_query_extension_reply_t *extension, ExtensionData *dataToFill)
{
if (!extension) {
return;
}
dataToFill->present = extension->present;
dataToFill->eventBase = extension->first_event;
dataToFill->errorBase = extension->first_error;
dataToFill->majorOpcode = extension->major_opcode;
}
int Extensions::damageNotifyEvent() const
{
return m_damage.eventBase + XCB_DAMAGE_NOTIFY;
}
bool Extensions::hasShape(xcb_window_t w) const
{
if (!isShapeAvailable()) {
return false;
}
UniqueCPtr<xcb_shape_query_extents_reply_t> extents(xcb_shape_query_extents_reply(
connection(), xcb_shape_query_extents_unchecked(connection(), w), nullptr));
if (!extents) {
return false;
}
return extents->bounding_shaped > 0;
}
bool Extensions::isCompositeOverlayAvailable() const
{
return m_composite.version >= 0x03; // 0.3
}
bool Extensions::isFixesRegionAvailable() const
{
return m_fixes.version >= 0x30; // 3
}
int Extensions::fixesCursorNotifyEvent() const
{
return m_fixes.eventBase + XCB_XFIXES_CURSOR_NOTIFY;
}
int Extensions::fixesSelectionNotifyEvent() const
{
return m_fixes.eventBase + XCB_XFIXES_SELECTION_NOTIFY;
}
bool Extensions::isShapeInputAvailable() const
{
return m_shape.version >= 0x11; // 1.1
}
int Extensions::randrNotifyEvent() const
{
return m_randr.eventBase + XCB_RANDR_SCREEN_CHANGE_NOTIFY;
}
int Extensions::shapeNotifyEvent() const
{
return m_shape.eventBase + XCB_SHAPE_NOTIFY;
}
int Extensions::syncAlarmNotifyEvent() const
{
return m_sync.eventBase + XCB_SYNC_ALARM_NOTIFY;
}
QList<ExtensionData> Extensions::extensions() const
{
return {
m_shape,
m_randr,
m_damage,
m_composite,
m_render,
m_fixes,
m_sync,
m_glx};
}
//****************************************
// Shm
//****************************************
Shm::Shm()
: m_shmId(-1)
, m_buffer(nullptr)
, m_segment(XCB_NONE)
, m_valid(false)
, m_pixmapFormat(XCB_IMAGE_FORMAT_XY_BITMAP)
{
m_valid = init();
}
Shm::~Shm()
{
if (m_valid) {
xcb_shm_detach(connection(), m_segment);
shmdt(m_buffer);
}
}
bool Shm::init()
{
const xcb_query_extension_reply_t *ext = xcb_get_extension_data(connection(), &xcb_shm_id);
if (!ext || !ext->present) {
qCDebug(KWIN_CORE) << "SHM extension not available";
return false;
}
UniqueCPtr<xcb_shm_query_version_reply_t> version(xcb_shm_query_version_reply(connection(),
xcb_shm_query_version_unchecked(connection()), nullptr));
if (!version) {
qCDebug(KWIN_CORE) << "Failed to get SHM extension version information";
return false;
}
m_pixmapFormat = version->pixmap_format;
const int MAXSIZE = 4096 * 2048 * 4; // TODO check there are not larger windows
m_shmId = shmget(IPC_PRIVATE, MAXSIZE, IPC_CREAT | 0600);
if (m_shmId < 0) {
qCDebug(KWIN_CORE) << "Failed to allocate SHM segment";
return false;
}
m_buffer = shmat(m_shmId, nullptr, 0 /*read/write*/);
if (-1 == reinterpret_cast<long>(m_buffer)) {
qCDebug(KWIN_CORE) << "Failed to attach SHM segment";
shmctl(m_shmId, IPC_RMID, nullptr);
return false;
}
shmctl(m_shmId, IPC_RMID, nullptr);
m_segment = xcb_generate_id(connection());
const xcb_void_cookie_t cookie = xcb_shm_attach_checked(connection(), m_segment, m_shmId, false);
UniqueCPtr<xcb_generic_error_t> error(xcb_request_check(connection(), cookie));
if (error) {
qCDebug(KWIN_CORE) << "xcb_shm_attach error: " << error->error_code;
shmdt(m_buffer);
return false;
}
return true;
}
uint32_t toXNative(qreal value)
{
//debug helper, check for things getting mangled
if (!qFuzzyIsNull(std::fmod(kwinApp()->xwaylandScale() * value, 1))) {
qCDebug(KWIN_CORE) << "precision lost! floating value sent to X" << kwinApp()->xwaylandScale() * value;
}
return static_cast<int32_t>(std::round(kwinApp()->xwaylandScale() * value));
}
QPoint toXNative(const QPointF &p)
{
return QPoint(toXNative(p.x()), toXNative(p.y()));
}
QSize toXNative(const QSizeF &s)
{
return QSize(toXNative(s.width()), toXNative(s.height()));
}
QRect toXNative(const QRectF &r)
{
return QRect(toXNative(r.x()), toXNative(r.y()), toXNative(r.width()), toXNative(r.height()));
}
qreal fromXNative(int value)
{
return value / kwinApp()->xwaylandScale();
}
QRectF fromXNative(const QRect &r)
{
return QRectF(fromXNative(r.x()), fromXNative(r.y()), fromXNative(r.width()), fromXNative(r.height()));
}
QSizeF fromXNative(const QSize &s)
{
return QSizeF(fromXNative(s.width()), fromXNative(s.height()));
}
qreal nativeRound(qreal value)
{
return fromXNative(toXNative(value));
}
static qreal nativeFloor(qreal value)
{
return std::floor(value * kwinApp()->xwaylandScale()) / kwinApp()->xwaylandScale();
}
QRectF nativeFloor(const QRectF &rect)
{
const auto output = workspace()->outputAt(rect.center());
const QRectF outputRect = output->mapFromGlobal(rect);
return output->mapToGlobal(QRectF(nativeFloor(outputRect.left()), nativeFloor(outputRect.top()),
nativeFloor(outputRect.width()), nativeFloor(outputRect.height())));
}
} // namespace Xcb
} // namespace KWin
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,61 @@
/*
SPDX-FileCopyrightText: 2024 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "utils/xcursorreader.h"
#include "3rdparty/xcursor.h"
#include <QFile>
namespace KWin
{
QList<CursorSprite> XCursorReader::load(const QString &filePath, int desiredSize, qreal devicePixelRatio)
{
QFile file(filePath);
if (!file.open(QFile::ReadOnly)) {
return {};
}
XcursorFile reader {
.closure = &file,
.read = [](XcursorFile *file, uint8_t *buffer, int len) -> int {
QFile *device = static_cast<QFile *>(file->closure);
return device->read(reinterpret_cast<char *>(buffer), len);
},
.skip = [](XcursorFile *file, long offset) -> XcursorBool {
QFile *device = static_cast<QFile *>(file->closure);
return device->skip(offset) != -1;
},
.seek = [](XcursorFile *file, long offset) -> XcursorBool {
QFile *device = static_cast<QFile *>(file->closure);
return device->seek(offset);
},
};
XcursorImages *images = XcursorXcFileLoadImages(&reader, desiredSize * devicePixelRatio);
if (!images) {
return {};
}
QList<CursorSprite> sprites;
for (int i = 0; i < images->nimage; ++i) {
const XcursorImage *nativeCursorImage = images->images[i];
const qreal scale = std::max(qreal(1), qreal(nativeCursorImage->size) / desiredSize);
const QPointF hotspot(nativeCursorImage->xhot, nativeCursorImage->yhot);
const std::chrono::milliseconds delay(nativeCursorImage->delay);
QImage data(nativeCursorImage->width, nativeCursorImage->height, QImage::Format_ARGB32_Premultiplied);
data.setDevicePixelRatio(scale);
memcpy(data.bits(), nativeCursorImage->pixels, data.sizeInBytes());
sprites.append(CursorSprite(data, hotspot / scale, delay));
}
XcursorImagesDestroy(images);
return sprites;
}
} // namespace KWin
@@ -0,0 +1,20 @@
/*
SPDX-FileCopyrightText: 2024 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "utils/cursortheme.h"
namespace KWin
{
class XCursorReader
{
public:
static QList<CursorSprite> load(const QString &filePath, int desiredSize, qreal devicePixelRatio);
};
} // namespace KWin