diff --git a/screens.cpp b/screens.cpp
index 2ad69d1e9d..48367944d6 100644
--- a/screens.cpp
+++ b/screens.cpp
@@ -22,6 +22,10 @@ along with this program. If not, see .
#include "cursor.h"
#include "settings.h"
#include "workspace.h"
+#if HAVE_WAYLAND
+#include "wayland_backend.h"
+#include
+#endif
#include
#include
@@ -34,7 +38,15 @@ Screens *Screens::s_self = nullptr;
Screens *Screens::create(QObject *parent)
{
Q_ASSERT(!s_self);
- s_self = new DesktopWidgetScreens(parent);
+#if HAVE_WAYLAND
+ if (kwinApp()->operationMode() == Application::OperationModeWaylandAndX11) {
+ s_self = new WaylandScreens(parent);
+ return s_self;
+ }
+#endif
+ if (kwinApp()->operationMode() == Application::OperationModeX11) {
+ s_self = new DesktopWidgetScreens(parent);
+ }
s_self->init();
return s_self;
}
@@ -197,4 +209,229 @@ void DesktopWidgetScreens::updateCount()
setCount(m_desktop->screenCount());
}
+#if HAVE_WAYLAND
+WaylandScreens::WaylandScreens(QObject* parent)
+ : Screens(parent)
+{
+}
+
+WaylandScreens::~WaylandScreens()
+{
+}
+
+void WaylandScreens::init()
+{
+ Screens::init();
+ connect(Wayland::WaylandBackend::self(), &Wayland::WaylandBackend::outputsChanged,
+ this, &WaylandScreens::startChangedTimer);
+ updateCount();
+}
+
+QRect WaylandScreens::geometry(int screen) const
+{
+ if (screen >= m_geometries.size()) {
+ return QRect();
+ }
+ return m_geometries.at(screen);
+}
+
+QSize WaylandScreens::size(int screen) const
+{
+ return geometry(screen).size();
+}
+
+int WaylandScreens::number(const QPoint &pos) const
+{
+ int bestScreen = 0;
+ int minDistance = INT_MAX;
+ for (int i = 0; i < m_geometries.size(); ++i) {
+ const QRect &geo = m_geometries.at(i);
+ if (geo.contains(pos)) {
+ return i;
+ }
+ int distance = QPoint(geo.topLeft() - pos).manhattanLength();
+ distance = qMin(distance, QPoint(geo.topRight() - pos).manhattanLength());
+ distance = qMin(distance, QPoint(geo.bottomRight() - pos).manhattanLength());
+ distance = qMin(distance, QPoint(geo.bottomLeft() - pos).manhattanLength());
+ if (distance < minDistance) {
+ minDistance = distance;
+ bestScreen = i;
+ }
+ }
+ return bestScreen;
+}
+
+void WaylandScreens::updateCount()
+{
+ m_geometries.clear();
+ int count = 0;
+ const QList &outputs = Wayland::WaylandBackend::self()->outputs();
+ for (auto it = outputs.begin(); it != outputs.end(); ++it) {
+ if ((*it)->pixelSize().isEmpty()) {
+ continue;
+ }
+ count++;
+ m_geometries.append(QRect((*it)->globalPosition(), (*it)->pixelSize()));
+ }
+ if (m_geometries.isEmpty()) {
+ // we need a fake screen
+ m_geometries.append(QRect(0, 0, displayWidth(), displayHeight()));
+ setCount(1);
+ return;
+ }
+ updateXRandr();
+ emit changed();
+}
+
+namespace RandR
+{
+typedef Xcb::Wrapper CurrentResources;
+}
+
+static bool setNewScreenSize(const QSize &size)
+{
+ auto c = xcb_randr_set_screen_size_checked(connection(), rootWindow(), size.width(), size.height(), 1, 1);
+ ScopedCPointer error(xcb_request_check(connection(), c));
+ if (!error.isNull()) {
+ qDebug() << "Error setting screen size: " << error->error_code;
+ return false;
+ }
+ return true;
+}
+
+static xcb_randr_crtc_t getCrtc(const xcb_randr_get_screen_resources_current_reply_t* r)
+{
+ if (xcb_randr_get_screen_resources_current_crtcs_length(r) == 0) {
+ qDebug() << "No CRTCs";
+ return XCB_NONE;
+ }
+ return xcb_randr_get_screen_resources_current_crtcs(r)[0];
+}
+
+static xcb_randr_output_t getOutputForCrtc(xcb_randr_crtc_t crtc)
+{
+ ScopedCPointer info(xcb_randr_get_crtc_info_reply(
+ connection(), xcb_randr_get_crtc_info(connection(), crtc, XCB_TIME_CURRENT_TIME), nullptr));
+ if (info->num_outputs == 0) {
+ return XCB_NONE;
+ }
+ return xcb_randr_get_crtc_info_outputs(info.data())[0];
+}
+
+static xcb_randr_mode_t createNewMode(const QSize &size)
+{
+ // need to create the new mode
+ qDebug() << "Creating a new mode";
+ QString name(QString::number(size.width()));
+ name.append('x');
+ name.append(QString::number(size.height()));
+ xcb_randr_mode_info_t newInfo;
+ newInfo.dot_clock = 0;
+ newInfo.height = size.height();
+ newInfo.hskew = 0;
+ newInfo.hsync_end = 0;
+ newInfo.hsync_start = 0;
+ newInfo.htotal = size.width();
+ newInfo.id = 0;
+ newInfo.mode_flags = 0;
+ newInfo.vsync_end = 0;
+ newInfo.vsync_start = 0;
+ newInfo.vtotal = size.height();
+ newInfo.width = size.width();
+ newInfo.name_len = name.length();
+ auto cookie = xcb_randr_create_mode_unchecked(connection(), rootWindow(), newInfo, name.length(), name.toUtf8().constData());
+ ScopedCPointer reply(xcb_randr_create_mode_reply(connection(), cookie, nullptr));
+ if (!reply.isNull()) {
+ return reply->mode;
+ }
+ return XCB_NONE;
+}
+
+static xcb_randr_mode_t getModeForSize(const QSize &size, const xcb_randr_get_screen_resources_current_reply_t* r)
+{
+ xcb_randr_mode_info_t *infos = xcb_randr_get_screen_resources_current_modes(r);
+ const int modeInfoLength = xcb_randr_get_screen_resources_current_modes_length(r);
+ // check available modes
+ for (int i = 0; i < modeInfoLength; ++i) {
+ xcb_randr_mode_info_t modeInfo = infos[i];
+ if (modeInfo.width == size.width() && modeInfo.height == size.height()) {
+ qDebug() << "Found our required mode";
+ return modeInfo.id;
+ }
+ }
+ // did not find our mode, so create it
+ return createNewMode(size);
+}
+
+static bool addModeToOutput(xcb_randr_output_t output, xcb_randr_mode_t mode)
+{
+ ScopedCPointer info(xcb_randr_get_output_info_reply(connection(),
+ xcb_randr_get_output_info(connection(), output, XCB_TIME_CURRENT_TIME), nullptr));
+ xcb_randr_mode_t *modes = xcb_randr_get_output_info_modes(info.data());
+ for (int i = 0; i < info->num_modes; ++i) {
+ if (modes[i] == mode) {
+ return true;
+ }
+ }
+ qDebug() << "Need to add the mode to output";
+ auto c = xcb_randr_add_output_mode_checked(connection(), output, mode);
+ ScopedCPointer error(xcb_request_check(connection(), c));
+ if (!error.isNull()) {
+ qDebug() << "Error while adding mode to output: " << error->error_code;
+ return false;
+ }
+ return true;
+}
+
+void WaylandScreens::updateXRandr()
+{
+ if (!Xcb::Extensions::self()->isRandrAvailable()) {
+ qDebug() << "No RandR extension available, cannot sync with X";
+ return;
+ }
+ QRegion screens;
+ foreach (const QRect &rect, m_geometries) {
+ screens = screens.united(rect);
+ }
+ const QSize &size = screens.boundingRect().size();
+ if (size.isEmpty()) {
+ return;
+ }
+
+ RandR::CurrentResources currentResources(rootWindow());
+ xcb_randr_crtc_t crtc = getCrtc(currentResources.data());
+ if (crtc == XCB_NONE) {
+ return;
+ }
+ xcb_randr_output_t output = getOutputForCrtc(crtc);
+ if (output == XCB_NONE) {
+ return;
+ }
+ // first disable the first CRTC
+ xcb_randr_set_crtc_config(connection(), crtc, XCB_TIME_CURRENT_TIME, XCB_TIME_CURRENT_TIME,
+ 0, 0, XCB_NONE, XCB_RANDR_ROTATION_ROTATE_0, 0, nullptr);
+
+ // then set new screen size
+ if (!setNewScreenSize(size)) {
+ return;
+ }
+
+ xcb_randr_mode_t mode = getModeForSize(size, currentResources.data());
+ if (mode == XCB_NONE) {
+ return;
+ }
+
+ if (!addModeToOutput(output, mode)) {
+ return;
+ }
+ // enable CRTC again
+ xcb_randr_set_crtc_config(connection(), crtc, XCB_TIME_CURRENT_TIME, XCB_TIME_CURRENT_TIME,
+ 0, 0, mode, XCB_RANDR_ROTATION_ROTATE_0, 1, &output);
+}
+
+#endif
+
} // namespace
diff --git a/screens.h b/screens.h
index 25e12c27f0..0d7e56476e 100644
--- a/screens.h
+++ b/screens.h
@@ -22,6 +22,7 @@ along with this program. If not, see .
// KWin includes
#include
+#include
// KDE includes
#include
#include
@@ -30,6 +31,7 @@ along with this program. If not, see .
#include
#include
+
class QDesktopWidget;
namespace KWin
@@ -159,6 +161,26 @@ private:
QDesktopWidget *m_desktop;
};
+#if HAVE_WAYLAND
+class WaylandScreens : public Screens
+{
+ Q_OBJECT
+public:
+ explicit WaylandScreens(QObject *parent);
+ virtual ~WaylandScreens();
+ void init() override;
+ QRect geometry(int screen) const override;
+ QSize size(int screen) const override;
+ int number(const QPoint& pos) const override;
+
+protected Q_SLOTS:
+ void updateCount() override;
+private:
+ void updateXRandr();
+ QList m_geometries;
+};
+#endif
+
inline
void Screens::setConfig(KSharedConfig::Ptr config)
{