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:
@@ -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 ®ion)
|
||||
{
|
||||
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 ×tamp, 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
|
||||
Reference in New Issue
Block a user