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) {