diff --git a/abstract_output.cpp b/abstract_output.cpp index 1536697c9..94d828285 100644 --- a/abstract_output.cpp +++ b/abstract_output.cpp @@ -1,39 +1,76 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2018 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ + #include "abstract_output.h" -// KF5 -#include +namespace KWin +{ -#include +GammaRamp::GammaRamp(uint32_t size) + : m_table(3 * size) + , m_size(size) +{ +} -namespace KWin +uint32_t GammaRamp::size() const { + return m_size; +} + +uint16_t *GammaRamp::red() +{ + return m_table.data(); +} + +const uint16_t *GammaRamp::red() const +{ + return m_table.data(); +} + +uint16_t *GammaRamp::green() +{ + return m_table.data() + m_size; +} + +const uint16_t *GammaRamp::green() const +{ + return m_table.data() + m_size; +} + +uint16_t *GammaRamp::blue() +{ + return m_table.data() + 2 * m_size; +} + +const uint16_t *GammaRamp::blue() const +{ + return m_table.data() + 2 * m_size; +} AbstractOutput::AbstractOutput(QObject *parent) : QObject(parent) { } AbstractOutput::~AbstractOutput() { } } diff --git a/abstract_output.h b/abstract_output.h index 63ae4ba1d..151febc7e 100644 --- a/abstract_output.h +++ b/abstract_output.h @@ -1,78 +1,129 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_ABSTRACT_OUTPUT_H #define KWIN_ABSTRACT_OUTPUT_H #include #include #include #include +#include namespace KWin { -namespace ColorCorrect { -struct GammaRamp; -} +class KWIN_EXPORT GammaRamp +{ +public: + GammaRamp(uint32_t size); + + /** + * Returns the size of the gamma ramp. + **/ + uint32_t size() const; + + /** + * Returns pointer to the first red component in the gamma ramp. + * + * The returned pointer can be used for altering the red component + * in the gamma ramp. + **/ + uint16_t *red(); + + /** + * Returns pointer to the first red component in the gamma ramp. + **/ + const uint16_t *red() const; + + /** + * Returns pointer to the first green component in the gamma ramp. + * + * The returned pointer can be used for altering the green component + * in the gamma ramp. + **/ + uint16_t *green(); + + /** + * Returns pointer to the first green component in the gamma ramp. + **/ + const uint16_t *green() const; + + /** + * Returns pointer to the first blue component in the gamma ramp. + * + * The returned pointer can be used for altering the blue component + * in the gamma ramp. + **/ + uint16_t *blue(); + + /** + * Returns pointer to the first blue component in the gamma ramp. + **/ + const uint16_t *blue() const; + +private: + QVector m_table; + uint32_t m_size; +}; /** * Generic output representation in a Wayland session **/ class KWIN_EXPORT AbstractOutput : public QObject { Q_OBJECT public: explicit AbstractOutput(QObject *parent = nullptr); virtual ~AbstractOutput(); virtual QString name() const = 0; virtual QRect geometry() const = 0; /** * Current refresh rate in 1/ms. **/ virtual int refreshRate() const = 0; virtual bool isInternal() const { return false; } virtual qreal scale() const { return 1.; } virtual QSize physicalSize() const { return QSize(); } virtual Qt::ScreenOrientation orientation() const { return Qt::PrimaryOrientation; } - virtual int getGammaRampSize() const { + virtual int gammaRampSize() const { return 0; } - virtual bool setGammaRamp(const ColorCorrect::GammaRamp &gamma) { + virtual bool setGammaRamp(const GammaRamp &gamma) { Q_UNUSED(gamma); return false; } }; } #endif diff --git a/colorcorrection/gammaramp.h b/colorcorrection/gammaramp.h deleted file mode 100644 index b215af168..000000000 --- a/colorcorrection/gammaramp.h +++ /dev/null @@ -1,53 +0,0 @@ -/******************************************************************** - KWin - the KDE window manager - This file is part of the KDE project. - -Copyright 2017 Roman Gilg - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*********************************************************************/ -#ifndef KWIN_GAMMARAMP_H -#define KWIN_GAMMARAMP_H - -namespace KWin -{ - -namespace ColorCorrect -{ - -struct GammaRamp { - GammaRamp(int _size) { - size = _size; - red = new uint16_t[3 * _size]; - green = red + _size; - blue = green + _size; - } - ~GammaRamp() { - delete[] red; - red = green = blue = nullptr; - } - - uint32_t size = 0; - uint16_t *red = nullptr; - uint16_t *green = nullptr; - uint16_t *blue = nullptr; - -private: - Q_DISABLE_COPY(GammaRamp) -}; - -} -} - -#endif // KWIN_GAMMARAMP_H diff --git a/colorcorrection/manager.cpp b/colorcorrection/manager.cpp index 0cd03693a..182265191 100644 --- a/colorcorrection/manager.cpp +++ b/colorcorrection/manager.cpp @@ -1,779 +1,781 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2017 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "manager.h" #include "colorcorrectdbusinterface.h" #include "suncalc.h" -#include "gammaramp.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_LINUX #include #endif #include #include namespace KWin { namespace ColorCorrect { static const int QUICK_ADJUST_DURATION = 2000; static const int TEMPERATURE_STEP = 50; static bool checkLocation(double lat, double lng) { return -90 <= lat && lat <= 90 && -180 <= lng && lng <= 180; } Manager::Manager(QObject *parent) : QObject(parent) { m_iface = new ColorCorrectDBusInterface(this); connect(kwinApp(), &Application::workspaceCreated, this, &Manager::init); } void Manager::init() { Settings::instance(kwinApp()->config()); // we may always read in the current config readConfig(); if (!kwinApp()->platform()->supportsGammaControl()) { // at least update the sun timings to make the values accessible via dbus updateSunTimings(true); return; } connect(Screens::self(), &Screens::countChanged, this, &Manager::hardReset); connect(LogindIntegration::self(), &LogindIntegration::sessionActiveChanged, this, [this](bool active) { if (active) { hardReset(); } else { cancelAllTimers(); } } ); #ifdef Q_OS_LINUX // monitor for system clock changes - from the time dataengine auto timeChangedFd = ::timerfd_create(CLOCK_REALTIME, O_CLOEXEC | O_NONBLOCK); ::itimerspec timespec; //set all timers to 0, which creates a timer that won't do anything ::memset(×pec, 0, sizeof(timespec)); // Monitor for the time changing (flags == TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET). // However these are not exposed in glibc so value is hardcoded: ::timerfd_settime(timeChangedFd, 3, ×pec, 0); connect(this, &QObject::destroyed, [timeChangedFd]() { ::close(timeChangedFd); }); auto notifier = new QSocketNotifier(timeChangedFd, QSocketNotifier::Read, this); connect(notifier, &QSocketNotifier::activated, this, [this](int fd) { uint64_t c; ::read(fd, &c, 8); // check if we're resuming from suspend - in this case do a hard reset // Note: We're using the time clock to detect a suspend phase instead of connecting to the // provided logind dbus signal, because this signal would be received way too late. QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.DBus.Properties", QStringLiteral("Get")); message.setArguments(QVariantList({"org.freedesktop.login1.Manager", QStringLiteral("PreparingForSleep")})); QDBusReply reply = QDBusConnection::systemBus().call(message); bool comingFromSuspend; if (reply.isValid()) { comingFromSuspend = reply.value().toBool(); } else { qCDebug(KWIN_COLORCORRECTION) << "Failed to get PreparingForSleep Property of logind session:" << reply.error().message(); // Always do a hard reset in case we have no further information. comingFromSuspend = true; } if (comingFromSuspend) { hardReset(); } else { resetAllTimers(); } }); #else // TODO: Alternative method for BSD. #endif hardReset(); } void Manager::hardReset() { cancelAllTimers(); updateSunTimings(true); if (kwinApp()->platform()->supportsGammaControl() && m_active) { m_running = true; commitGammaRamps(currentTargetTemp()); } resetAllTimers(); } void Manager::reparseConfigAndReset() { cancelAllTimers(); readConfig(); hardReset(); } void Manager::readConfig() { Settings *s = Settings::self(); s->load(); m_active = s->active(); NightColorMode mode = s->mode(); if (mode == NightColorMode::Location || mode == NightColorMode::Timings) { m_mode = mode; } else { // also fallback for invalid setting values m_mode = NightColorMode::Automatic; } m_nightTargetTemp = qBound(MIN_TEMPERATURE, s->nightTemperature(), NEUTRAL_TEMPERATURE); double lat, lng; auto correctReadin = [&lat, &lng]() { if (!checkLocation(lat, lng)) { // out of domain lat = 0; lng = 0; } }; // automatic lat = s->latitudeAuto(); lng = s->longitudeAuto(); correctReadin(); m_latAuto = lat; m_lngAuto = lng; // fixed location lat = s->latitudeFixed(); lng = s->longitudeFixed(); correctReadin(); m_latFixed = lat; m_lngFixed = lng; // fixed timings QTime mrB = QTime::fromString(s->morningBeginFixed(), "hhmm"); QTime evB = QTime::fromString(s->eveningBeginFixed(), "hhmm"); int diffME = mrB.msecsTo(evB); if (diffME <= 0) { // morning not strictly before evening - use defaults mrB = QTime(6,0); evB = QTime(18,0); diffME = mrB.msecsTo(evB); } int diffMin = qMin(diffME, MSC_DAY - diffME); int trTime = s->transitionTime() * 1000 * 60; if (trTime < 0 || diffMin <= trTime) { // transition time too long - use defaults mrB = QTime(6,0); evB = QTime(18,0); trTime = FALLBACK_SLOW_UPDATE_TIME; } m_morning = mrB; m_evening = evB; m_trTime = qMax(trTime / 1000 / 60, 1); } void Manager::resetAllTimers() { cancelAllTimers(); if (kwinApp()->platform()->supportsGammaControl()) { if (m_active) { m_running = true; } // we do this also for active being false in order to reset the temperature back to the day value resetQuickAdjustTimer(); } else { m_running = false; } } void Manager::cancelAllTimers() { delete m_slowUpdateStartTimer; delete m_slowUpdateTimer; delete m_quickAdjustTimer; m_slowUpdateStartTimer = nullptr; m_slowUpdateTimer = nullptr; m_quickAdjustTimer = nullptr; } void Manager::resetQuickAdjustTimer() { updateSunTimings(false); int tempDiff = qAbs(currentTargetTemp() - m_currentTemp); // allow tolerance of one TEMPERATURE_STEP to compensate if a slow update is coincidental if (tempDiff > TEMPERATURE_STEP) { cancelAllTimers(); m_quickAdjustTimer = new QTimer(this); m_quickAdjustTimer->setSingleShot(false); connect(m_quickAdjustTimer, &QTimer::timeout, this, &Manager::quickAdjust); int interval = QUICK_ADJUST_DURATION / (tempDiff / TEMPERATURE_STEP); if (interval == 0) { interval = 1; } m_quickAdjustTimer->start(interval); } else { resetSlowUpdateStartTimer(); } } void Manager::quickAdjust() { if (!m_quickAdjustTimer) { return; } int nextTemp; int targetTemp = currentTargetTemp(); if (m_currentTemp < targetTemp) { nextTemp = qMin(m_currentTemp + TEMPERATURE_STEP, targetTemp); } else { nextTemp = qMax(m_currentTemp - TEMPERATURE_STEP, targetTemp); } commitGammaRamps(nextTemp); if (nextTemp == targetTemp) { // stop timer, we reached the target temp delete m_quickAdjustTimer; m_quickAdjustTimer = nullptr; resetSlowUpdateStartTimer(); } } void Manager::resetSlowUpdateStartTimer() { delete m_slowUpdateStartTimer; m_slowUpdateStartTimer = nullptr; if (!m_running || m_quickAdjustTimer) { // only reenable the slow update start timer when quick adjust is not active anymore return; } // set up the next slow update m_slowUpdateStartTimer = new QTimer(this); m_slowUpdateStartTimer->setSingleShot(true); connect(m_slowUpdateStartTimer, &QTimer::timeout, this, &Manager::resetSlowUpdateStartTimer); updateSunTimings(false); int diff; if (m_mode == NightColorMode::Timings) { // Timings mode is in local time diff = QDateTime::currentDateTime().msecsTo(m_next.first); } else { diff = QDateTime::currentDateTimeUtc().msecsTo(m_next.first); } if (diff <= 0) { qCCritical(KWIN_COLORCORRECTION) << "Error in time calculation. Deactivating Night Color."; return; } m_slowUpdateStartTimer->start(diff); // start the current slow update resetSlowUpdateTimer(); } void Manager::resetSlowUpdateTimer() { delete m_slowUpdateTimer; m_slowUpdateTimer = nullptr; QDateTime now = QDateTime::currentDateTimeUtc(); bool isDay = daylight(); int targetTemp = isDay ? m_dayTargetTemp : m_nightTargetTemp; if (m_prev.first == m_prev.second) { // transition time is zero commitGammaRamps(isDay ? m_dayTargetTemp : m_nightTargetTemp); return; } if (m_prev.first <= now && now <= m_prev.second) { int availTime = now.msecsTo(m_prev.second); m_slowUpdateTimer = new QTimer(this); m_slowUpdateTimer->setSingleShot(false); if (isDay) { connect(m_slowUpdateTimer, &QTimer::timeout, this, [this]() {slowUpdate(m_dayTargetTemp);}); } else { connect(m_slowUpdateTimer, &QTimer::timeout, this, [this]() {slowUpdate(m_nightTargetTemp);}); } // calculate interval such as temperature is changed by TEMPERATURE_STEP K per timer timeout int interval = availTime / (qAbs(targetTemp - m_currentTemp) / TEMPERATURE_STEP); if (interval == 0) { interval = 1; } m_slowUpdateTimer->start(interval); } } void Manager::slowUpdate(int targetTemp) { if (!m_slowUpdateTimer) { return; } int nextTemp; if (m_currentTemp < targetTemp) { nextTemp = qMin(m_currentTemp + TEMPERATURE_STEP, targetTemp); } else { nextTemp = qMax(m_currentTemp - TEMPERATURE_STEP, targetTemp); } commitGammaRamps(nextTemp); if (nextTemp == targetTemp) { // stop timer, we reached the target temp delete m_slowUpdateTimer; m_slowUpdateTimer = nullptr; } } void Manager::updateSunTimings(bool force) { QDateTime todayNow = QDateTime::currentDateTimeUtc(); if (m_mode == NightColorMode::Timings) { QDateTime todayNowLocal = QDateTime::currentDateTime(); QDateTime morB = QDateTime(todayNowLocal.date(), m_morning); QDateTime morE = morB.addSecs(m_trTime * 60); QDateTime eveB = QDateTime(todayNowLocal.date(), m_evening); QDateTime eveE = eveB.addSecs(m_trTime * 60); if (morB <= todayNowLocal && todayNowLocal < eveB) { m_next = DateTimes(eveB, eveE); m_prev = DateTimes(morB, morE); } else if (todayNowLocal < morB) { m_next = DateTimes(morB, morE); m_prev = DateTimes(eveB.addDays(-1), eveE.addDays(-1)); } else { m_next = DateTimes(morB.addDays(1), morE.addDays(1)); m_prev = DateTimes(eveB, eveE); } return; } double lat, lng; if (m_mode == NightColorMode::Automatic) { lat = m_latAuto; lng = m_lngAuto; } else { lat = m_latFixed; lng = m_lngFixed; } if (!force) { // first try by only switching the timings if (daylight()) { // next is morning m_prev = m_next; m_next = getSunTimings(todayNow.date().addDays(1), lat, lng, true); } else { // next is evening m_prev = m_next; m_next = getSunTimings(todayNow.date(), lat, lng, false); } } if (force || !checkAutomaticSunTimings()) { // in case this fails, reset them DateTimes morning = getSunTimings(todayNow.date(), lat, lng, true); if (todayNow < morning.first) { m_prev = getSunTimings(todayNow.date().addDays(-1), lat, lng, false); m_next = morning; } else { DateTimes evening = getSunTimings(todayNow.date(), lat, lng, false); if (todayNow < evening.first) { m_prev = morning; m_next = evening; } else { m_prev = evening; m_next = getSunTimings(todayNow.date().addDays(1), lat, lng, true); } } } } DateTimes Manager::getSunTimings(QDate date, double latitude, double longitude, bool morning) const { Times times = calculateSunTimings(date, latitude, longitude, morning); // At locations near the poles it is possible, that we can't // calculate some or all sun timings (midnight sun). // In this case try to fallback to sensible default values. bool beginDefined = !times.first.isNull(); bool endDefined = !times.second.isNull(); if (!beginDefined || !endDefined) { if (beginDefined) { times.second = times.first.addMSecs( FALLBACK_SLOW_UPDATE_TIME ); } else if (endDefined) { times.first = times.second.addMSecs( - FALLBACK_SLOW_UPDATE_TIME); } else { // Just use default values for morning and evening, but the user // will probably deactivate Night Color anyway if he is living // in a region without clear sun rise and set. times.first = morning ? QTime(6,0,0) : QTime(18,0,0); times.second = times.first.addMSecs( FALLBACK_SLOW_UPDATE_TIME ); } } return DateTimes(QDateTime(date, times.first, Qt::UTC), QDateTime(date, times.second, Qt::UTC)); } bool Manager::checkAutomaticSunTimings() const { if (m_prev.first.isValid() && m_prev.second.isValid() && m_next.first.isValid() && m_next.second.isValid()) { QDateTime todayNow = QDateTime::currentDateTimeUtc(); return m_prev.first <= todayNow && todayNow < m_next.first && m_prev.first.msecsTo(m_next.first) < MSC_DAY * 23./24; } return false; } bool Manager::daylight() const { return m_prev.first.date() == m_next.first.date(); } int Manager::currentTargetTemp() const { if (!m_active) { return NEUTRAL_TEMPERATURE; } QDateTime todayNow = QDateTime::currentDateTimeUtc(); auto f = [this, todayNow](int target1, int target2) { if (todayNow <= m_prev.second) { double residueQuota = todayNow.msecsTo(m_prev.second) / (double)m_prev.first.msecsTo(m_prev.second); double ret = (int)((1. - residueQuota) * (double)target2 + residueQuota * (double)target1); // remove single digits ret = ((int)(0.1 * ret)) * 10; return (int)ret; } else { return target2; } }; if (daylight()) { return f(m_nightTargetTemp, m_dayTargetTemp); } else { return f(m_dayTargetTemp, m_nightTargetTemp); } } void Manager::commitGammaRamps(int temperature) { const auto outs = kwinApp()->platform()->outputs(); for (auto *o : outs) { - int rampsize = o->getGammaRampSize(); + int rampsize = o->gammaRampSize(); GammaRamp ramp(rampsize); /* * The gamma calculation below is based on the Redshift app: * https://github.com/jonls/redshift */ + uint16_t *red = ramp.red(); + uint16_t *green = ramp.green(); + uint16_t *blue = ramp.blue(); // linear default state for (int i = 0; i < rampsize; i++) { uint16_t value = (double)i / rampsize * (UINT16_MAX + 1); - ramp.red[i] = value; - ramp.green[i] = value; - ramp.blue[i] = value; + red[i] = value; + green[i] = value; + blue[i] = value; } // approximate white point float whitePoint[3]; float alpha = (temperature % 100) / 100.; int bbCIndex = ((temperature - 1000) / 100) * 3; whitePoint[0] = (1. - alpha) * blackbodyColor[bbCIndex] + alpha * blackbodyColor[bbCIndex + 3]; whitePoint[1] = (1. - alpha) * blackbodyColor[bbCIndex + 1] + alpha * blackbodyColor[bbCIndex + 4]; whitePoint[2] = (1. - alpha) * blackbodyColor[bbCIndex + 2] + alpha * blackbodyColor[bbCIndex + 5]; for (int i = 0; i < rampsize; i++) { - ramp.red[i] = (double)ramp.red[i] / (UINT16_MAX+1) * whitePoint[0] * (UINT16_MAX+1); - ramp.green[i] = (double)ramp.green[i] / (UINT16_MAX+1) * whitePoint[1] * (UINT16_MAX+1); - ramp.blue[i] = (double)ramp.blue[i] / (UINT16_MAX+1) * whitePoint[2] * (UINT16_MAX+1); + red[i] = qreal(red[i]) / (UINT16_MAX+1) * whitePoint[0] * (UINT16_MAX+1); + green[i] = qreal(green[i]) / (UINT16_MAX+1) * whitePoint[1] * (UINT16_MAX+1); + blue[i] = qreal(blue[i]) / (UINT16_MAX+1) * whitePoint[2] * (UINT16_MAX+1); } if (o->setGammaRamp(ramp)) { m_currentTemp = temperature; m_failedCommitAttempts = 0; } else { m_failedCommitAttempts++; if (m_failedCommitAttempts < 10) { qCWarning(KWIN_COLORCORRECTION).nospace() << "Committing Gamma Ramp failed for output " << o->name() << ". Trying " << (10 - m_failedCommitAttempts) << " times more."; } else { // TODO: On multi monitor setups we could try to rollback earlier changes for already commited outputs qCWarning(KWIN_COLORCORRECTION) << "Gamma Ramp commit failed too often. Deactivating color correction for now."; m_failedCommitAttempts = 0; // reset so we can try again later (i.e. after suspend phase or config change) m_running = false; cancelAllTimers(); } } } } QHash Manager::info() const { return QHash { { QStringLiteral("Available"), kwinApp()->platform()->supportsGammaControl() }, { QStringLiteral("ActiveEnabled"), true}, { QStringLiteral("Active"), m_active}, { QStringLiteral("ModeEnabled"), true}, { QStringLiteral("Mode"), (int)m_mode}, { QStringLiteral("NightTemperatureEnabled"), true}, { QStringLiteral("NightTemperature"), m_nightTargetTemp}, { QStringLiteral("Running"), m_running}, { QStringLiteral("CurrentColorTemperature"), m_currentTemp}, { QStringLiteral("LatitudeAuto"), m_latAuto}, { QStringLiteral("LongitudeAuto"), m_lngAuto}, { QStringLiteral("LocationEnabled"), true}, { QStringLiteral("LatitudeFixed"), m_latFixed}, { QStringLiteral("LongitudeFixed"), m_lngFixed}, { QStringLiteral("TimingsEnabled"), true}, { QStringLiteral("MorningBeginFixed"), m_morning.toString(Qt::ISODate)}, { QStringLiteral("EveningBeginFixed"), m_evening.toString(Qt::ISODate)}, { QStringLiteral("TransitionTime"), m_trTime}, }; } bool Manager::changeConfiguration(QHash data) { bool activeUpdate, modeUpdate, tempUpdate, locUpdate, timeUpdate; activeUpdate = modeUpdate = tempUpdate = locUpdate = timeUpdate = false; bool active = m_active; NightColorMode mode = m_mode; int nightT = m_nightTargetTemp; double lat = m_latFixed; double lng = m_lngFixed; QTime mor = m_morning; QTime eve = m_evening; int trT = m_trTime; QHash::const_iterator iter1, iter2, iter3; iter1 = data.constFind("Active"); if (iter1 != data.constEnd()) { if (!iter1.value().canConvert()) { return false; } bool act = iter1.value().toBool(); activeUpdate = m_active != act; active = act; } iter1 = data.constFind("Mode"); if (iter1 != data.constEnd()) { if (!iter1.value().canConvert()) { return false; } int mo = iter1.value().toInt(); if (mo < 0 || 2 < mo) { return false; } NightColorMode moM; switch (mo) { case 0: moM = NightColorMode::Automatic; break; case 1: moM = NightColorMode::Location; break; case 2: moM = NightColorMode::Timings; } modeUpdate = m_mode != moM; mode = moM; } iter1 = data.constFind("NightTemperature"); if (iter1 != data.constEnd()) { if (!iter1.value().canConvert()) { return false; } int nT = iter1.value().toInt(); if (nT < MIN_TEMPERATURE || NEUTRAL_TEMPERATURE < nT) { return false; } tempUpdate = m_nightTargetTemp != nT; nightT = nT; } iter1 = data.constFind("LatitudeFixed"); iter2 = data.constFind("LongitudeFixed"); if (iter1 != data.constEnd() && iter2 != data.constEnd()) { if (!iter1.value().canConvert() || !iter2.value().canConvert()) { return false; } double la = iter1.value().toDouble(); double ln = iter2.value().toDouble(); if (!checkLocation(la, ln)) { return false; } locUpdate = m_latFixed != la || m_lngFixed != ln; lat = la; lng = ln; } iter1 = data.constFind("MorningBeginFixed"); iter2 = data.constFind("EveningBeginFixed"); iter3 = data.constFind("TransitionTime"); if (iter1 != data.constEnd() && iter2 != data.constEnd() && iter3 != data.constEnd()) { if (!iter1.value().canConvert() || !iter2.value().canConvert() || !iter3.value().canConvert()) { return false; } QTime mo = QTime::fromString(iter1.value().toString(), Qt::ISODate); QTime ev = QTime::fromString(iter2.value().toString(), Qt::ISODate); if (!mo.isValid() || !ev.isValid()) { return false; } int tT = iter3.value().toInt(); int diffME = mo.msecsTo(ev); if (diffME <= 0 || qMin(diffME, MSC_DAY - diffME) <= tT * 60 * 1000 || tT < 1) { // morning not strictly before evening, transition time too long or transition time out of bounds return false; } timeUpdate = m_morning != mo || m_evening != ev || m_trTime != tT; mor = mo; eve = ev; trT = tT; } if (!(activeUpdate || modeUpdate || tempUpdate || locUpdate || timeUpdate)) { return true; } bool resetNeeded = activeUpdate || modeUpdate || tempUpdate || (locUpdate && mode == NightColorMode::Location) || (timeUpdate && mode == NightColorMode::Timings); if (resetNeeded) { cancelAllTimers(); } Settings *s = Settings::self(); if (activeUpdate) { m_active = active; s->setActive(active); } if (modeUpdate) { m_mode = mode; s->setMode(mode); } if (tempUpdate) { m_nightTargetTemp = nightT; s->setNightTemperature(nightT); } if (locUpdate) { m_latFixed = lat; m_lngFixed = lng; s->setLatitudeFixed(lat); s->setLongitudeFixed(lng); } if (timeUpdate) { m_morning = mor; m_evening = eve; m_trTime = trT; s->setMorningBeginFixed(mor.toString("hhmm")); s->setEveningBeginFixed(eve.toString("hhmm")); s->setTransitionTime(trT); } s->save(); if (resetNeeded) { resetAllTimers(); } emit configChange(info()); return true; } void Manager::autoLocationUpdate(double latitude, double longitude) { if (!checkLocation(latitude, longitude)) { return; } // we tolerate small deviations with minimal impact on sun timings if (qAbs(m_latAuto - latitude) < 2 && qAbs(m_lngAuto - longitude) < 1) { return; } cancelAllTimers(); m_latAuto = latitude; m_lngAuto = longitude; Settings *s = Settings::self(); s->setLatitudeAuto(latitude); s->setLongitudeAuto(longitude); s->save(); resetAllTimers(); emit configChange(info()); } } } diff --git a/plugins/platforms/drm/drm_object_crtc.cpp b/plugins/platforms/drm/drm_object_crtc.cpp index 245734fd0..34ae31349 100644 --- a/plugins/platforms/drm/drm_object_crtc.cpp +++ b/plugins/platforms/drm/drm_object_crtc.cpp @@ -1,123 +1,128 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "drm_object_crtc.h" #include "drm_backend.h" #include "drm_output.h" #include "drm_buffer.h" #include "drm_pointer.h" #include "logging.h" -#include namespace KWin { DrmCrtc::DrmCrtc(uint32_t crtc_id, DrmBackend *backend, int resIndex) : DrmObject(crtc_id, backend->fd()), m_resIndex(resIndex), m_backend(backend) { DrmScopedPointer modeCrtc(drmModeGetCrtc(backend->fd(), crtc_id)); if (modeCrtc) { m_gammaRampSize = modeCrtc->gamma_size; } } DrmCrtc::~DrmCrtc() { } bool DrmCrtc::atomicInit() { qCDebug(KWIN_DRM) << "Atomic init for CRTC:" << resIndex() << "id:" << m_id; if (!initProps()) { return false; } return true; } bool DrmCrtc::initProps() { setPropertyNames({ QByteArrayLiteral("MODE_ID"), QByteArrayLiteral("ACTIVE"), }); DrmScopedPointer properties( drmModeObjectGetProperties(fd(), m_id, DRM_MODE_OBJECT_CRTC)); if (!properties) { qCWarning(KWIN_DRM) << "Failed to get properties for crtc " << m_id ; return false; } int propCount = int(PropertyIndex::Count); for (int j = 0; j < propCount; ++j) { initProp(j, properties.data()); } return true; } void DrmCrtc::flipBuffer() { if (m_currentBuffer && m_backend->deleteBufferAfterPageFlip() && m_currentBuffer != m_nextBuffer) { delete m_currentBuffer; } m_currentBuffer = m_nextBuffer; m_nextBuffer = nullptr; delete m_blackBuffer; m_blackBuffer = nullptr; } bool DrmCrtc::blank() { if (!m_output) { return false; } if (!m_blackBuffer) { DrmDumbBuffer *blackBuffer = m_backend->createBuffer(m_output->pixelSize()); if (!blackBuffer->map()) { delete blackBuffer; return false; } blackBuffer->image()->fill(Qt::black); m_blackBuffer = blackBuffer; } if (m_output->setModeLegacy(m_blackBuffer)) { if (m_currentBuffer && m_backend->deleteBufferAfterPageFlip()) { delete m_currentBuffer; delete m_nextBuffer; } m_currentBuffer = nullptr; m_nextBuffer = nullptr; return true; } return false; } -bool DrmCrtc::setGammaRamp(const ColorCorrect::GammaRamp &gamma) { - bool isError = drmModeCrtcSetGamma(m_backend->fd(), m_id, gamma.size, - gamma.red, gamma.green, gamma.blue); +bool DrmCrtc::setGammaRamp(const GammaRamp &gamma) +{ + uint16_t *red = const_cast(gamma.red()); + uint16_t *green = const_cast(gamma.green()); + uint16_t *blue = const_cast(gamma.blue()); + + const bool isError = drmModeCrtcSetGamma(m_backend->fd(), m_id, + gamma.size(), red, green, blue); + return !isError; } } diff --git a/plugins/platforms/drm/drm_object_crtc.h b/plugins/platforms/drm/drm_object_crtc.h index c5f77e0e1..1f6fe1169 100644 --- a/plugins/platforms/drm/drm_object_crtc.h +++ b/plugins/platforms/drm/drm_object_crtc.h @@ -1,88 +1,85 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_DRM_OBJECT_CRTC_H #define KWIN_DRM_OBJECT_CRTC_H #include "drm_object.h" namespace KWin { -namespace ColorCorrect { -struct GammaRamp; -} - class DrmBackend; class DrmBuffer; class DrmDumbBuffer; +class GammaRamp; class DrmCrtc : public DrmObject { public: DrmCrtc(uint32_t crtc_id, DrmBackend *backend, int resIndex); virtual ~DrmCrtc(); bool atomicInit(); enum class PropertyIndex { ModeId = 0, Active, Count }; - + bool initProps(); int resIndex() const { return m_resIndex; } DrmBuffer *current() { return m_currentBuffer; } DrmBuffer *next() { return m_nextBuffer; } void setNext(DrmBuffer *buffer) { m_nextBuffer = buffer; } void flipBuffer(); bool blank(); - int getGammaRampSize() const { + int gammaRampSize() const { return m_gammaRampSize; } - bool setGammaRamp(const ColorCorrect::GammaRamp &gamma); + bool setGammaRamp(const GammaRamp &gamma); private: int m_resIndex; uint32_t m_gammaRampSize = 0; DrmBuffer *m_currentBuffer = nullptr; DrmBuffer *m_nextBuffer = nullptr; DrmDumbBuffer *m_blackBuffer = nullptr; DrmBackend *m_backend; }; } #endif diff --git a/plugins/platforms/drm/drm_output.cpp b/plugins/platforms/drm/drm_output.cpp index 1bce5d3a5..1b72c0992 100644 --- a/plugins/platforms/drm/drm_output.cpp +++ b/plugins/platforms/drm/drm_output.cpp @@ -1,1214 +1,1214 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "drm_output.h" #include "drm_backend.h" #include "drm_object_plane.h" #include "drm_object_crtc.h" #include "drm_object_connector.h" #include #include "composite.h" #include "logind.h" #include "logging.h" #include "main.h" #include "orientation_sensor.h" #include "screens_drm.h" #include "wayland_server.h" // KWayland #include // KF5 #include #include #include // Qt #include #include #include // drm #include #include #include namespace KWin { DrmOutput::DrmOutput(DrmBackend *backend) : AbstractWaylandOutput(backend) , m_backend(backend) { } DrmOutput::~DrmOutput() { Q_ASSERT(!m_pageFlipPending); if (!m_deleted) { teardown(); } } void DrmOutput::teardown() { m_deleted = true; hideCursor(); m_crtc->blank(); if (m_primaryPlane) { // TODO: when having multiple planes, also clean up these m_primaryPlane->setOutput(nullptr); if (m_backend->deleteBufferAfterPageFlip()) { delete m_primaryPlane->current(); } m_primaryPlane->setCurrent(nullptr); } m_crtc->setOutput(nullptr); m_conn->setOutput(nullptr); delete m_cursor[0]; delete m_cursor[1]; if (!m_pageFlipPending) { deleteLater(); } //else will be deleted in the page flip handler //this is needed so that the pageflipcallback handle isn't deleted } void DrmOutput::releaseGbm() { if (DrmBuffer *b = m_crtc->current()) { b->releaseGbm(); } if (m_primaryPlane && m_primaryPlane->current()) { m_primaryPlane->current()->releaseGbm(); } } bool DrmOutput::hideCursor() { return drmModeSetCursor(m_backend->fd(), m_crtc->id(), 0, 0, 0) == 0; } bool DrmOutput::showCursor(DrmDumbBuffer *c) { const QSize &s = c->size(); return drmModeSetCursor(m_backend->fd(), m_crtc->id(), c->handle(), s.width(), s.height()) == 0; } bool DrmOutput::showCursor() { const bool ret = showCursor(m_cursor[m_cursorIndex]); if (!ret) { return ret; } if (m_hasNewCursor) { m_cursorIndex = (m_cursorIndex + 1) % 2; m_hasNewCursor = false; } return ret; } int orientationToRotation(Qt::ScreenOrientation orientation) { switch (orientation) { case Qt::PrimaryOrientation: case Qt::LandscapeOrientation: return 0; case Qt::InvertedPortraitOrientation: return 90; case Qt::InvertedLandscapeOrientation: return 180; case Qt::PortraitOrientation: return 270; } Q_UNREACHABLE(); return 0; } QMatrix4x4 DrmOutput::matrixDisplay(const QSize &s) const { QMatrix4x4 matrix; const int angle = orientationToRotation(orientation()); if (angle) { const QSize center = s / 2; matrix.translate(center.width(), center.height()); matrix.rotate(angle, 0, 0, 1); matrix.translate(-center.width(), -center.height()); } matrix.scale(scale()); return matrix; } void DrmOutput::updateCursor() { QImage cursorImage = m_backend->softwareCursor(); if (cursorImage.isNull()) { return; } m_hasNewCursor = true; QImage *c = m_cursor[m_cursorIndex]->image(); c->fill(Qt::transparent); QPainter p; p.begin(c); p.setWorldTransform(matrixDisplay(QSize(cursorImage.width(), cursorImage.height())).toTransform()); p.drawImage(QPoint(0, 0), cursorImage); p.end(); } void DrmOutput::moveCursor(const QPoint &globalPos) { const QMatrix4x4 hotspotMatrix = matrixDisplay(m_backend->softwareCursor().size()); QPoint p = globalPos - AbstractWaylandOutput::globalPos(); switch (orientation()) { case Qt::PrimaryOrientation: case Qt::LandscapeOrientation: break; case Qt::PortraitOrientation: p = QPoint(p.y(), pixelSize().height() - p.x()); break; case Qt::InvertedPortraitOrientation: p = QPoint(pixelSize().width() - p.y(), p.x()); break; case Qt::InvertedLandscapeOrientation: p = QPoint(pixelSize().width() - p.x(), pixelSize().height() - p.y()); break; } p *= scale(); p -= hotspotMatrix.map(m_backend->softwareCursorHotspot()); drmModeMoveCursor(m_backend->fd(), m_crtc->id(), p.x(), p.y()); } static QHash s_connectorNames = { {DRM_MODE_CONNECTOR_Unknown, QByteArrayLiteral("Unknown")}, {DRM_MODE_CONNECTOR_VGA, QByteArrayLiteral("VGA")}, {DRM_MODE_CONNECTOR_DVII, QByteArrayLiteral("DVI-I")}, {DRM_MODE_CONNECTOR_DVID, QByteArrayLiteral("DVI-D")}, {DRM_MODE_CONNECTOR_DVIA, QByteArrayLiteral("DVI-A")}, {DRM_MODE_CONNECTOR_Composite, QByteArrayLiteral("Composite")}, {DRM_MODE_CONNECTOR_SVIDEO, QByteArrayLiteral("SVIDEO")}, {DRM_MODE_CONNECTOR_LVDS, QByteArrayLiteral("LVDS")}, {DRM_MODE_CONNECTOR_Component, QByteArrayLiteral("Component")}, {DRM_MODE_CONNECTOR_9PinDIN, QByteArrayLiteral("DIN")}, {DRM_MODE_CONNECTOR_DisplayPort, QByteArrayLiteral("DP")}, {DRM_MODE_CONNECTOR_HDMIA, QByteArrayLiteral("HDMI-A")}, {DRM_MODE_CONNECTOR_HDMIB, QByteArrayLiteral("HDMI-B")}, {DRM_MODE_CONNECTOR_TV, QByteArrayLiteral("TV")}, {DRM_MODE_CONNECTOR_eDP, QByteArrayLiteral("eDP")}, {DRM_MODE_CONNECTOR_VIRTUAL, QByteArrayLiteral("Virtual")}, {DRM_MODE_CONNECTOR_DSI, QByteArrayLiteral("DSI")}, #ifdef DRM_MODE_CONNECTOR_DPI {DRM_MODE_CONNECTOR_DPI, QByteArrayLiteral("DPI")}, #endif }; namespace { quint64 refreshRateForMode(_drmModeModeInfo *m) { // Calculate higher precision (mHz) refresh rate // logic based on Weston, see compositor-drm.c quint64 refreshRate = (m->clock * 1000000LL / m->htotal + m->vtotal / 2) / m->vtotal; if (m->flags & DRM_MODE_FLAG_INTERLACE) { refreshRate *= 2; } if (m->flags & DRM_MODE_FLAG_DBLSCAN) { refreshRate /= 2; } if (m->vscan > 1) { refreshRate /= m->vscan; } return refreshRate; } } bool DrmOutput::init(drmModeConnector *connector) { initEdid(connector); initDpms(connector); initUuid(); if (m_backend->atomicModeSetting()) { if (!initPrimaryPlane()) { return false; } } else if (!m_crtc->blank()) { return false; } setInternal(connector->connector_type == DRM_MODE_CONNECTOR_LVDS || connector->connector_type == DRM_MODE_CONNECTOR_eDP); setDpmsSupported(true); if (isInternal()) { connect(kwinApp(), &Application::screensCreated, this, [this] { connect(screens()->orientationSensor(), &OrientationSensor::orientationChanged, this, &DrmOutput::automaticRotation); } ); } QSize physicalSize = !m_edid.physicalSize.isEmpty() ? m_edid.physicalSize : QSize(connector->mmWidth, connector->mmHeight); // the size might be completely borked. E.g. Samsung SyncMaster 2494HS reports 160x90 while in truth it's 520x292 // as this information is used to calculate DPI info, it's going to result in everything being huge const QByteArray unknown = QByteArrayLiteral("unknown"); KConfigGroup group = kwinApp()->config()->group("EdidOverwrite").group(m_edid.eisaId.isEmpty() ? unknown : m_edid.eisaId) .group(m_edid.monitorName.isEmpty() ? unknown : m_edid.monitorName) .group(m_edid.serialNumber.isEmpty() ? unknown : m_edid.serialNumber); if (group.hasKey("PhysicalSize")) { const QSize overwriteSize = group.readEntry("PhysicalSize", physicalSize); qCWarning(KWIN_DRM) << "Overwriting monitor physical size for" << m_edid.eisaId << "/" << m_edid.monitorName << "/" << m_edid.serialNumber << " from " << physicalSize << "to " << overwriteSize; physicalSize = overwriteSize; } setRawPhysicalSize(physicalSize); initOutputDevice(connector); setEnabled(true); return true; } void DrmOutput::initUuid() { QCryptographicHash hash(QCryptographicHash::Md5); hash.addData(QByteArray::number(m_conn->id())); hash.addData(m_edid.eisaId); hash.addData(m_edid.monitorName); hash.addData(m_edid.serialNumber); m_uuid = hash.result().toHex().left(10); } void DrmOutput::initOutputDevice(drmModeConnector *connector) { QString manufacturer; if (!m_edid.eisaId.isEmpty()) { manufacturer = QString::fromLatin1(m_edid.eisaId); } QString connectorName = s_connectorNames.value(connector->connector_type, QByteArrayLiteral("Unknown")); QString modelName; if (!m_edid.monitorName.isEmpty()) { QString m = QString::fromLatin1(m_edid.monitorName); if (!m_edid.serialNumber.isEmpty()) { m.append('/'); m.append(QString::fromLatin1(m_edid.serialNumber)); } modelName = m; } else if (!m_edid.serialNumber.isEmpty()) { modelName = QString::fromLatin1(m_edid.serialNumber); } else { modelName = i18n("unknown"); } const QString model = connectorName + QStringLiteral("-") + QString::number(connector->connector_type_id) + QStringLiteral("-") + modelName; // read in mode information QVector modes; for (int i = 0; i < connector->count_modes; ++i) { // TODO: in AMS here we could read and store for later every mode's blob_id // would simplify isCurrentMode(..) and presentAtomically(..) in case of mode set auto *m = &connector->modes[i]; KWayland::Server::OutputDeviceInterface::ModeFlags deviceflags; if (isCurrentMode(m)) { deviceflags |= KWayland::Server::OutputDeviceInterface::ModeFlag::Current; } if (m->type & DRM_MODE_TYPE_PREFERRED) { deviceflags |= KWayland::Server::OutputDeviceInterface::ModeFlag::Preferred; } KWayland::Server::OutputDeviceInterface::Mode mode; mode.id = i; mode.size = QSize(m->hdisplay, m->vdisplay); mode.flags = deviceflags; mode.refreshRate = refreshRateForMode(m); modes << mode; } AbstractWaylandOutput::initWaylandOutputDevice(model, manufacturer, m_uuid, modes); } bool DrmOutput::isCurrentMode(const drmModeModeInfo *mode) const { return mode->clock == m_mode.clock && mode->hdisplay == m_mode.hdisplay && mode->hsync_start == m_mode.hsync_start && mode->hsync_end == m_mode.hsync_end && mode->htotal == m_mode.htotal && mode->hskew == m_mode.hskew && mode->vdisplay == m_mode.vdisplay && mode->vsync_start == m_mode.vsync_start && mode->vsync_end == m_mode.vsync_end && mode->vtotal == m_mode.vtotal && mode->vscan == m_mode.vscan && mode->vrefresh == m_mode.vrefresh && mode->flags == m_mode.flags && mode->type == m_mode.type && qstrcmp(mode->name, m_mode.name) == 0; } static bool verifyEdidHeader(drmModePropertyBlobPtr edid) { const uint8_t *data = reinterpret_cast(edid->data); if (data[0] != 0x00) { return false; } for (int i = 1; i < 7; ++i) { if (data[i] != 0xFF) { return false; } } if (data[7] != 0x00) { return false; } return true; } static QByteArray extractEisaId(drmModePropertyBlobPtr edid) { /* * From EDID standard section 3.4: * The ID Manufacturer Name field, shown in Table 3.5, contains a 2-byte representation of the monitor's * manufacturer. This is the same as the EISA ID. It is based on compressed ASCII, “0001=A” ... “11010=Z”. * * The table: * | 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 uint8_t *data = reinterpret_cast(edid->data); static const uint offset = 0x8; char id[4]; if (data[offset] >> 7) { // bit at position 7 is not a 0 return QByteArray(); } // shift two bits to right, and with 7 right most bits id[0] = 'A' + ((data[offset] >> 2) & 0x1f) -1; // for first byte: take last two bits and shift them 3 to left (000xx000) // for second byte: shift 5 bits to right and take 3 right most bits (00000xxx) // or both together id[1] = 'A' + (((data[offset] & 0x3) << 3) | ((data[offset + 1] >> 5) & 0x7)) - 1; // take five right most bits id[2] = 'A' + (data[offset + 1] & 0x1f) - 1; id[3] = '\0'; return QByteArray(id); } static void extractMonitorDescriptorDescription(drmModePropertyBlobPtr blob, DrmOutput::Edid &edid) { // see section 3.10.3 const uint8_t *data = reinterpret_cast(blob->data); static const uint offset = 0x36; static const uint blockLength = 18; for (int i = 0; i < 5; ++i) { const uint co = offset + i * blockLength; // Flag = 0000h when block used as descriptor if (data[co] != 0) { continue; } if (data[co + 1] != 0) { continue; } // Reserved = 00h when block used as descriptor if (data[co + 2] != 0) { continue; } /* * FFh: Monitor Serial Number - Stored as ASCII, code page # 437, ≤ 13 bytes. * FEh: ASCII String - Stored as ASCII, code page # 437, ≤ 13 bytes. * FDh: Monitor range limits, binary coded * FCh: Monitor name, stored as ASCII, code page # 437 * FBh: Descriptor contains additional color point data * FAh: Descriptor contains additional Standard Timing Identifications * F9h - 11h: Currently undefined * 10h: Dummy descriptor, used to indicate that the descriptor space is unused * 0Fh - 00h: Descriptor defined by manufacturer. */ if (data[co + 3] == 0xfc && edid.monitorName.isEmpty()) { edid.monitorName = QByteArray((const char *)(&data[co + 5]), 12).trimmed(); } if (data[co + 3] == 0xfe) { const QByteArray id = QByteArray((const char *)(&data[co + 5]), 12).trimmed(); if (!id.isEmpty()) { edid.eisaId = id; } } if (data[co + 3] == 0xff) { edid.serialNumber = QByteArray((const char *)(&data[co + 5]), 12).trimmed(); } } } static QByteArray extractSerialNumber(drmModePropertyBlobPtr edid) { // see section 3.4 const uint8_t *data = reinterpret_cast(edid->data); static const uint offset = 0x0C; /* * The ID serial number is a 32-bit serial number used to differentiate between individual instances of the same model * of monitor. Its use is optional. When used, the bit order for this field follows that shown in Table 3.6. The EDID * structure Version 1 Revision 1 and later offer a way to represent the serial number of the monitor as an ASCII string * in a separate descriptor block. */ uint32_t serialNumber = 0; serialNumber = (uint32_t) data[offset + 0]; serialNumber |= (uint32_t) data[offset + 1] << 8; serialNumber |= (uint32_t) data[offset + 2] << 16; serialNumber |= (uint32_t) data[offset + 3] << 24; if (serialNumber == 0) { return QByteArray(); } return QByteArray::number(serialNumber); } static QSize extractPhysicalSize(drmModePropertyBlobPtr edid) { const uint8_t *data = reinterpret_cast(edid->data); return QSize(data[0x15], data[0x16]) * 10; } void DrmOutput::initEdid(drmModeConnector *connector) { DrmScopedPointer edid; for (int i = 0; i < connector->count_props; ++i) { DrmScopedPointer property(drmModeGetProperty(m_backend->fd(), connector->props[i])); if (!property) { continue; } if ((property->flags & DRM_MODE_PROP_BLOB) && qstrcmp(property->name, "EDID") == 0) { edid.reset(drmModeGetPropertyBlob(m_backend->fd(), connector->prop_values[i])); } } if (!edid) { return; } // for documentation see: http://read.pudn.com/downloads110/ebook/456020/E-EDID%20Standard.pdf if (edid->length < 128) { return; } if (!verifyEdidHeader(edid.data())) { return; } m_edid.eisaId = extractEisaId(edid.data()); m_edid.serialNumber = extractSerialNumber(edid.data()); // parse monitor descriptor description extractMonitorDescriptorDescription(edid.data(), m_edid); m_edid.physicalSize = extractPhysicalSize(edid.data()); } bool DrmOutput::initPrimaryPlane() { for (int i = 0; i < m_backend->planes().size(); ++i) { DrmPlane* p = m_backend->planes()[i]; if (!p) { continue; } if (p->type() != DrmPlane::TypeIndex::Primary) { continue; } if (p->output()) { // Plane already has an output continue; } if (m_primaryPlane) { // Output already has a primary plane continue; } if (!p->isCrtcSupported(m_crtc->resIndex())) { continue; } p->setOutput(this); m_primaryPlane = p; qCDebug(KWIN_DRM) << "Initialized primary plane" << p->id() << "on CRTC" << m_crtc->id(); return true; } qCCritical(KWIN_DRM) << "Failed to initialize primary plane."; return false; } bool DrmOutput::initCursorPlane() // TODO: Add call in init (but needs layer support in general first) { for (int i = 0; i < m_backend->planes().size(); ++i) { DrmPlane* p = m_backend->planes()[i]; if (!p) { continue; } if (p->type() != DrmPlane::TypeIndex::Cursor) { continue; } if (p->output()) { // Plane already has an output continue; } if (m_cursorPlane) { // Output already has a cursor plane continue; } if (!p->isCrtcSupported(m_crtc->resIndex())) { continue; } p->setOutput(this); m_cursorPlane = p; qCDebug(KWIN_DRM) << "Initialized cursor plane" << p->id() << "on CRTC" << m_crtc->id(); return true; } return false; } bool DrmOutput::initCursor(const QSize &cursorSize) { auto createCursor = [this, cursorSize] (int index) { m_cursor[index] = m_backend->createBuffer(cursorSize); if (!m_cursor[index]->map(QImage::Format_ARGB32_Premultiplied)) { return false; } return true; }; if (!createCursor(0) || !createCursor(1)) { return false; } return true; } void DrmOutput::initDpms(drmModeConnector *connector) { for (int i = 0; i < connector->count_props; ++i) { DrmScopedPointer property(drmModeGetProperty(m_backend->fd(), connector->props[i])); if (!property) { continue; } if (qstrcmp(property->name, "DPMS") == 0) { m_dpms.swap(property); break; } } } static DrmOutput::DpmsMode fromWaylandDpmsMode(KWayland::Server::OutputInterface::DpmsMode wlMode) { using namespace KWayland::Server; switch (wlMode) { case OutputInterface::DpmsMode::On: return DrmOutput::DpmsMode::On; case OutputInterface::DpmsMode::Standby: return DrmOutput::DpmsMode::Standby; case OutputInterface::DpmsMode::Suspend: return DrmOutput::DpmsMode::Suspend; case OutputInterface::DpmsMode::Off: return DrmOutput::DpmsMode::Off; default: Q_UNREACHABLE(); } } static KWayland::Server::OutputInterface::DpmsMode toWaylandDpmsMode(DrmOutput::DpmsMode mode) { using namespace KWayland::Server; switch (mode) { case DrmOutput::DpmsMode::On: return OutputInterface::DpmsMode::On; case DrmOutput::DpmsMode::Standby: return OutputInterface::DpmsMode::Standby; case DrmOutput::DpmsMode::Suspend: return OutputInterface::DpmsMode::Suspend; case DrmOutput::DpmsMode::Off: return OutputInterface::DpmsMode::Off; default: Q_UNREACHABLE(); } } void DrmOutput::updateDpms(KWayland::Server::OutputInterface::DpmsMode mode) { if (m_dpms.isNull()) { return; } const auto drmMode = fromWaylandDpmsMode(mode); if (drmMode == m_dpmsModePending) { qCDebug(KWIN_DRM) << "New DPMS mode equals old mode. DPMS unchanged."; return; } m_dpmsModePending = drmMode; if (m_backend->atomicModeSetting()) { m_modesetRequested = true; if (drmMode == DpmsMode::On) { if (m_pageFlipPending) { m_pageFlipPending = false; Compositor::self()->bufferSwapComplete(); } dpmsOnHandler(); } else { m_dpmsAtomicOffPending = true; if (!m_pageFlipPending) { dpmsAtomicOff(); } } } else { if (drmModeConnectorSetProperty(m_backend->fd(), m_conn->id(), m_dpms->prop_id, uint64_t(drmMode)) < 0) { m_dpmsModePending = m_dpmsMode; qCWarning(KWIN_DRM) << "Setting DPMS failed"; return; } if (drmMode == DpmsMode::On) { dpmsOnHandler(); } else { dpmsOffHandler(); } m_dpmsMode = m_dpmsModePending; } } void DrmOutput::dpmsOnHandler() { qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to On."; auto wlOutput = waylandOutput(); if (wlOutput) { wlOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsModePending)); } emit dpmsChanged(); m_backend->checkOutputsAreOn(); if (!m_backend->atomicModeSetting()) { m_crtc->blank(); } if (Compositor *compositor = Compositor::self()) { compositor->addRepaintFull(); } } void DrmOutput::dpmsOffHandler() { qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to Off."; auto wlOutput = waylandOutput(); if (wlOutput) { wlOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsModePending)); } emit dpmsChanged(); m_backend->outputWentOff(); } void DrmOutput::transform(KWayland::Server::OutputDeviceInterface::Transform transform) { waylandOutputDevice()->setTransform(transform); using KWayland::Server::OutputDeviceInterface; using KWayland::Server::OutputInterface; auto wlOutput = waylandOutput(); switch (transform) { case OutputDeviceInterface::Transform::Normal: if (m_primaryPlane) { m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate0); } if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Normal); } setOrientation(Qt::PrimaryOrientation); break; case OutputDeviceInterface::Transform::Rotated90: if (m_primaryPlane) { m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate90); } if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Rotated90); } setOrientation(Qt::PortraitOrientation); break; case OutputDeviceInterface::Transform::Rotated180: if (m_primaryPlane) { m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate180); } if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Rotated180); } setOrientation(Qt::InvertedLandscapeOrientation); break; case OutputDeviceInterface::Transform::Rotated270: if (m_primaryPlane) { m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate270); } if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Rotated270); } setOrientation(Qt::InvertedPortraitOrientation); break; case OutputDeviceInterface::Transform::Flipped: // TODO: what is this exactly? if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Flipped); } break; case OutputDeviceInterface::Transform::Flipped90: // TODO: what is this exactly? if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Flipped90); } break; case OutputDeviceInterface::Transform::Flipped180: // TODO: what is this exactly? if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Flipped180); } break; case OutputDeviceInterface::Transform::Flipped270: // TODO: what is this exactly? if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Flipped270); } break; } m_modesetRequested = true; // the cursor might need to get rotated updateCursor(); showCursor(); // TODO: are these calls not enough in updateMode already? setWaylandMode(); } void DrmOutput::updateMode(int modeIndex) { // get all modes on the connector DrmScopedPointer connector(drmModeGetConnector(m_backend->fd(), m_conn->id())); if (connector->count_modes <= modeIndex) { // TODO: error? return; } if (isCurrentMode(&connector->modes[modeIndex])) { // nothing to do return; } m_mode = connector->modes[modeIndex]; m_modesetRequested = true; setWaylandMode(); } QSize DrmOutput::pixelSize() const { return orientateSize(QSize(m_mode.hdisplay, m_mode.vdisplay)); } void DrmOutput::setWaylandMode() { AbstractWaylandOutput::setWaylandMode(QSize(m_mode.hdisplay, m_mode.vdisplay), refreshRateForMode(&m_mode)); } void DrmOutput::pageFlipped() { m_pageFlipPending = false; if (m_deleted) { deleteLater(); return; } if (!m_crtc) { return; } // Egl based surface buffers get destroyed, QPainter based dumb buffers not // TODO: split up DrmOutput in two for dumb and egl/gbm surface buffer compatible subclasses completely? if (m_backend->deleteBufferAfterPageFlip()) { if (m_backend->atomicModeSetting()) { if (!m_primaryPlane->next()) { // on manual vt switch // TODO: when we later use overlay planes it might happen, that we have a page flip with only // damage on one of these, and therefore the primary plane has no next buffer // -> Then we don't want to return here! if (m_primaryPlane->current()) { m_primaryPlane->current()->releaseGbm(); } return; } for (DrmPlane *p : m_nextPlanesFlipList) { p->flipBufferWithDelete(); } m_nextPlanesFlipList.clear(); } else { if (!m_crtc->next()) { // on manual vt switch if (DrmBuffer *b = m_crtc->current()) { b->releaseGbm(); } } m_crtc->flipBuffer(); } } else { if (m_backend->atomicModeSetting()){ for (DrmPlane *p : m_nextPlanesFlipList) { p->flipBuffer(); } m_nextPlanesFlipList.clear(); } else { m_crtc->flipBuffer(); } m_crtc->flipBuffer(); } } bool DrmOutput::present(DrmBuffer *buffer) { if (m_backend->atomicModeSetting()) { return presentAtomically(buffer); } else { return presentLegacy(buffer); } } bool DrmOutput::dpmsAtomicOff() { m_dpmsAtomicOffPending = false; // TODO: With multiple planes: deactivate all of them here delete m_primaryPlane->next(); m_primaryPlane->setNext(nullptr); m_nextPlanesFlipList << m_primaryPlane; if (!doAtomicCommit(AtomicCommitMode::Test)) { qCDebug(KWIN_DRM) << "Atomic test commit to Dpms Off failed. Aborting."; return false; } if (!doAtomicCommit(AtomicCommitMode::Real)) { qCDebug(KWIN_DRM) << "Atomic commit to Dpms Off failed. This should have never happened! Aborting."; return false; } m_nextPlanesFlipList.clear(); dpmsOffHandler(); return true; } bool DrmOutput::presentAtomically(DrmBuffer *buffer) { if (!LogindIntegration::self()->isActiveSession()) { qCWarning(KWIN_DRM) << "Logind session not active."; return false; } if (m_pageFlipPending) { qCWarning(KWIN_DRM) << "Page not yet flipped."; return false; } #if HAVE_EGL_STREAMS if (m_backend->useEglStreams() && !m_modesetRequested) { // EglStreamBackend queues normal page flips through EGL, // modesets are still performed through DRM-KMS m_pageFlipPending = true; return true; } #endif m_primaryPlane->setNext(buffer); m_nextPlanesFlipList << m_primaryPlane; if (!doAtomicCommit(AtomicCommitMode::Test)) { //TODO: When we use planes for layered rendering, fallback to renderer instead. Also for direct scanout? //TODO: Probably should undo setNext and reset the flip list qCDebug(KWIN_DRM) << "Atomic test commit failed. Aborting present."; // go back to previous state if (m_lastWorkingState.valid) { m_mode = m_lastWorkingState.mode; setOrientation(m_lastWorkingState.orientation); setGlobalPos(m_lastWorkingState.globalPos); if (m_primaryPlane) { m_primaryPlane->setTransformation(m_lastWorkingState.planeTransformations); } m_modesetRequested = true; // the cursor might need to get rotated updateCursor(); showCursor(); // TODO: forward to OutputInterface and OutputDeviceInterface setWaylandMode(); emit screens()->changed(); } return false; } const bool wasModeset = m_modesetRequested; if (!doAtomicCommit(AtomicCommitMode::Real)) { qCDebug(KWIN_DRM) << "Atomic commit failed. This should have never happened! Aborting present."; //TODO: Probably should undo setNext and reset the flip list return false; } if (wasModeset) { // store current mode set as new good state m_lastWorkingState.mode = m_mode; m_lastWorkingState.orientation = orientation(); m_lastWorkingState.globalPos = globalPos(); if (m_primaryPlane) { m_lastWorkingState.planeTransformations = m_primaryPlane->transformation(); } m_lastWorkingState.valid = true; } m_pageFlipPending = true; return true; } bool DrmOutput::presentLegacy(DrmBuffer *buffer) { if (m_crtc->next()) { return false; } if (!LogindIntegration::self()->isActiveSession()) { m_crtc->setNext(buffer); return false; } if (m_dpmsMode != DpmsMode::On) { return false; } // Do we need to set a new mode first? if (!m_crtc->current() || m_crtc->current()->needsModeChange(buffer)) { if (!setModeLegacy(buffer)) { return false; } } const bool ok = drmModePageFlip(m_backend->fd(), m_crtc->id(), buffer->bufferId(), DRM_MODE_PAGE_FLIP_EVENT, this) == 0; if (ok) { m_crtc->setNext(buffer); } else { qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno); } return ok; } bool DrmOutput::setModeLegacy(DrmBuffer *buffer) { uint32_t connId = m_conn->id(); if (drmModeSetCrtc(m_backend->fd(), m_crtc->id(), buffer->bufferId(), 0, 0, &connId, 1, &m_mode) == 0) { return true; } else { qCWarning(KWIN_DRM) << "Mode setting failed"; return false; } } bool DrmOutput::doAtomicCommit(AtomicCommitMode mode) { drmModeAtomicReq *req = drmModeAtomicAlloc(); auto errorHandler = [this, mode, req] () { if (mode == AtomicCommitMode::Test) { // TODO: when we later test overlay planes, make sure we change only the right stuff back } if (req) { drmModeAtomicFree(req); } if (m_dpmsMode != m_dpmsModePending) { qCWarning(KWIN_DRM) << "Setting DPMS failed"; m_dpmsModePending = m_dpmsMode; if (m_dpmsMode != DpmsMode::On) { dpmsOffHandler(); } } // TODO: see above, rework later for overlay planes! for (DrmPlane *p : m_nextPlanesFlipList) { p->setNext(nullptr); } m_nextPlanesFlipList.clear(); }; if (!req) { qCWarning(KWIN_DRM) << "DRM: couldn't allocate atomic request"; errorHandler(); return false; } uint32_t flags = 0; // Do we need to set a new mode? if (m_modesetRequested) { if (m_dpmsModePending == DpmsMode::On) { if (drmModeCreatePropertyBlob(m_backend->fd(), &m_mode, sizeof(m_mode), &m_blobId) != 0) { qCWarning(KWIN_DRM) << "Failed to create property blob"; errorHandler(); return false; } } if (!atomicReqModesetPopulate(req, m_dpmsModePending == DpmsMode::On)){ qCWarning(KWIN_DRM) << "Failed to populate Atomic Modeset"; errorHandler(); return false; } flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; } if (mode == AtomicCommitMode::Real) { if (m_dpmsModePending == DpmsMode::On) { if (!(flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) { // TODO: Evaluating this condition should only be necessary, as long as we expect older kernels than 4.10. flags |= DRM_MODE_ATOMIC_NONBLOCK; } #if HAVE_EGL_STREAMS if (!m_backend->useEglStreams()) // EglStreamBackend uses the NV_output_drm_flip_event EGL extension // to register the flip event through eglStreamConsumerAcquireAttribNV #endif flags |= DRM_MODE_PAGE_FLIP_EVENT; } } else { flags |= DRM_MODE_ATOMIC_TEST_ONLY; } bool ret = true; // TODO: Make sure when we use more than one plane at a time, that we go through this list in the right order. for (int i = m_nextPlanesFlipList.size() - 1; 0 <= i; i-- ) { DrmPlane *p = m_nextPlanesFlipList[i]; ret &= p->atomicPopulate(req); } if (!ret) { qCWarning(KWIN_DRM) << "Failed to populate atomic planes. Abort atomic commit!"; errorHandler(); return false; } if (drmModeAtomicCommit(m_backend->fd(), req, flags, this)) { qCWarning(KWIN_DRM) << "Atomic request failed to commit:" << strerror(errno); errorHandler(); return false; } if (mode == AtomicCommitMode::Real && (flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) { qCDebug(KWIN_DRM) << "Atomic Modeset successful."; m_modesetRequested = false; m_dpmsMode = m_dpmsModePending; } drmModeAtomicFree(req); return true; } bool DrmOutput::atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable) { if (enable) { m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcX), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcY), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcW), m_mode.hdisplay << 16); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcH), m_mode.vdisplay << 16); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcW), m_mode.hdisplay); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcH), m_mode.vdisplay); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcId), m_crtc->id()); } else { if (m_backend->deleteBufferAfterPageFlip()) { delete m_primaryPlane->current(); delete m_primaryPlane->next(); } m_primaryPlane->setCurrent(nullptr); m_primaryPlane->setNext(nullptr); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcX), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcY), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcW), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcH), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcW), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcH), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcId), 0); } m_conn->setValue(int(DrmConnector::PropertyIndex::CrtcId), enable ? m_crtc->id() : 0); m_crtc->setValue(int(DrmCrtc::PropertyIndex::ModeId), enable ? m_blobId : 0); m_crtc->setValue(int(DrmCrtc::PropertyIndex::Active), enable); bool ret = true; ret &= m_conn->atomicPopulate(req); ret &= m_crtc->atomicPopulate(req); return ret; } bool DrmOutput::supportsTransformations() const { if (!m_primaryPlane) { return false; } const auto transformations = m_primaryPlane->supportedTransformations(); return transformations.testFlag(DrmPlane::Transformation::Rotate90) || transformations.testFlag(DrmPlane::Transformation::Rotate180) || transformations.testFlag(DrmPlane::Transformation::Rotate270); } void DrmOutput::automaticRotation() { if (!m_primaryPlane) { return; } const auto supportedTransformations = m_primaryPlane->supportedTransformations(); const auto requestedTransformation = screens()->orientationSensor()->orientation(); using KWayland::Server::OutputDeviceInterface; OutputDeviceInterface::Transform newTransformation = OutputDeviceInterface::Transform::Normal; switch (requestedTransformation) { case OrientationSensor::Orientation::TopUp: newTransformation = OutputDeviceInterface::Transform::Normal; break; case OrientationSensor::Orientation::TopDown: if (!supportedTransformations.testFlag(DrmPlane::Transformation::Rotate180)) { return; } newTransformation = OutputDeviceInterface::Transform::Rotated180; break; case OrientationSensor::Orientation::LeftUp: if (!supportedTransformations.testFlag(DrmPlane::Transformation::Rotate90)) { return; } newTransformation = OutputDeviceInterface::Transform::Rotated90; break; case OrientationSensor::Orientation::RightUp: if (!supportedTransformations.testFlag(DrmPlane::Transformation::Rotate270)) { return; } newTransformation = OutputDeviceInterface::Transform::Rotated270; break; case OrientationSensor::Orientation::FaceUp: case OrientationSensor::Orientation::FaceDown: case OrientationSensor::Orientation::Undefined: // unsupported return; } transform(newTransformation); emit screens()->changed(); } -int DrmOutput::getGammaRampSize() const +int DrmOutput::gammaRampSize() const { - return m_crtc->getGammaRampSize(); + return m_crtc->gammaRampSize(); } -bool DrmOutput::setGammaRamp(const ColorCorrect::GammaRamp &gamma) +bool DrmOutput::setGammaRamp(const GammaRamp &gamma) { return m_crtc->setGammaRamp(gamma); } } diff --git a/plugins/platforms/drm/drm_output.h b/plugins/platforms/drm/drm_output.h index c2199e677..c84f16927 100644 --- a/plugins/platforms/drm/drm_output.h +++ b/plugins/platforms/drm/drm_output.h @@ -1,177 +1,177 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_DRM_OUTPUT_H #define KWIN_DRM_OUTPUT_H #include "abstract_wayland_output.h" #include "drm_pointer.h" #include "drm_object.h" #include "drm_object_plane.h" #include #include #include #include #include namespace KWin { class DrmBackend; class DrmBuffer; class DrmDumbBuffer; class DrmPlane; class DrmConnector; class DrmCrtc; class KWIN_EXPORT DrmOutput : public AbstractWaylandOutput { Q_OBJECT public: struct Edid { QByteArray eisaId; QByteArray monitorName; QByteArray serialNumber; QSize physicalSize; }; ///deletes the output, calling this whilst a page flip is pending will result in an error ~DrmOutput() override; ///queues deleting the output after a page flip has completed. void teardown(); void releaseGbm(); bool showCursor(DrmDumbBuffer *buffer); bool showCursor(); bool hideCursor(); void updateCursor(); void moveCursor(const QPoint &globalPos); bool init(drmModeConnector *connector); bool present(DrmBuffer *buffer); void pageFlipped(); QSize pixelSize() const override; // These values are defined by the kernel enum class DpmsMode { On = DRM_MODE_DPMS_ON, Standby = DRM_MODE_DPMS_STANDBY, Suspend = DRM_MODE_DPMS_SUSPEND, Off = DRM_MODE_DPMS_OFF }; bool isDpmsEnabled() const { // We care for current as well as pending mode in order to allow first present in AMS. return m_dpmsModePending == DpmsMode::On; } QByteArray uuid() const { return m_uuid; } const DrmCrtc *crtc() const { return m_crtc; } const DrmPlane *primaryPlane() const { return m_primaryPlane; } bool initCursor(const QSize &cursorSize); bool supportsTransformations() const; Q_SIGNALS: void dpmsChanged(); private: friend class DrmBackend; friend class DrmCrtc; // TODO: For use of setModeLegacy. Remove later when we allow multiple connectors per crtc // and save the connector ids in the DrmCrtc instance. DrmOutput(DrmBackend *backend); bool presentAtomically(DrmBuffer *buffer); enum class AtomicCommitMode { Test, Real }; bool doAtomicCommit(AtomicCommitMode mode); bool presentLegacy(DrmBuffer *buffer); bool setModeLegacy(DrmBuffer *buffer); void initEdid(drmModeConnector *connector); void initDpms(drmModeConnector *connector); void initOutputDevice(drmModeConnector *connector); bool isCurrentMode(const drmModeModeInfo *mode) const; void initUuid(); bool initPrimaryPlane(); bool initCursorPlane(); void dpmsOnHandler(); void dpmsOffHandler(); bool dpmsAtomicOff(); bool atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable); void updateDpms(KWayland::Server::OutputInterface::DpmsMode mode) override; void updateMode(int modeIndex) override; void setWaylandMode(); void transform(KWayland::Server::OutputDeviceInterface::Transform transform) override; void automaticRotation(); - int getGammaRampSize() const override; - bool setGammaRamp(const ColorCorrect::GammaRamp &gamma) override; + int gammaRampSize() const override; + bool setGammaRamp(const GammaRamp &gamma) override; QMatrix4x4 matrixDisplay(const QSize &s) const; DrmBackend *m_backend; DrmConnector *m_conn = nullptr; DrmCrtc *m_crtc = nullptr; bool m_lastGbm = false; drmModeModeInfo m_mode; Edid m_edid; DrmScopedPointer m_dpms; DpmsMode m_dpmsMode = DpmsMode::On; DpmsMode m_dpmsModePending = DpmsMode::On; QByteArray m_uuid; uint32_t m_blobId = 0; DrmPlane* m_primaryPlane = nullptr; DrmPlane* m_cursorPlane = nullptr; QVector m_nextPlanesFlipList; bool m_pageFlipPending = false; bool m_dpmsAtomicOffPending = false; bool m_modesetRequested = true; struct { Qt::ScreenOrientation orientation; drmModeModeInfo mode; DrmPlane::Transformations planeTransformations; QPoint globalPos; bool valid = false; } m_lastWorkingState; DrmDumbBuffer *m_cursor[2] = {nullptr, nullptr}; int m_cursorIndex = 0; bool m_hasNewCursor = false; bool m_deleted = false; }; } Q_DECLARE_METATYPE(KWin::DrmOutput*) #endif diff --git a/plugins/platforms/virtual/virtual_output.h b/plugins/platforms/virtual/virtual_output.h index 4569c7dd0..5ddb9beaf 100644 --- a/plugins/platforms/virtual/virtual_output.h +++ b/plugins/platforms/virtual/virtual_output.h @@ -1,64 +1,64 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2018 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_VIRTUAL_OUTPUT_H #define KWIN_VIRTUAL_OUTPUT_H #include "abstract_wayland_output.h" #include #include namespace KWin { class VirtualBackend; class VirtualOutput : public AbstractWaylandOutput { Q_OBJECT public: VirtualOutput(QObject *parent = nullptr); virtual ~VirtualOutput(); QSize pixelSize() const override; void setGeometry(const QRect &geo); - int getGammaRampSize() const override { + int gammaRampSize() const override { return m_gammaSize; } - bool setGammaRamp(const ColorCorrect::GammaRamp &gamma) override { + bool setGammaRamp(const GammaRamp &gamma) override { Q_UNUSED(gamma); return m_gammaResult; } private: Q_DISABLE_COPY(VirtualOutput); friend class VirtualBackend; QSize m_pixelSize; int m_gammaSize = 200; bool m_gammaResult = true; }; } #endif diff --git a/plugins/platforms/x11/standalone/x11_output.cpp b/plugins/platforms/x11/standalone/x11_output.cpp index 9a1e30a08..d86aa1bb7 100644 --- a/plugins/platforms/x11/standalone/x11_output.cpp +++ b/plugins/platforms/x11/standalone/x11_output.cpp @@ -1,64 +1,91 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "x11_output.h" #include "screens.h" namespace KWin { X11Output::X11Output(QObject *parent) : AbstractOutput(parent) { } QString X11Output::name() const { return m_name; } void X11Output::setName(QString set) { m_name = set; } QRect X11Output::geometry() const { if (m_geometry.isValid()) { return m_geometry; } return QRect(QPoint(0, 0), Screens::self()->displaySize()); // xinerama, lacks RandR } void X11Output::setGeometry(QRect set) { m_geometry = set; } int X11Output::refreshRate() const { return m_refreshRate; } void X11Output::setRefreshRate(int set) { m_refreshRate = set; } +int X11Output::gammaRampSize() const +{ + return m_gammaRampSize; +} + +bool X11Output::setGammaRamp(const GammaRamp &gamma) +{ + if (m_crtc == XCB_NONE) { + return false; + } + + xcb_randr_set_crtc_gamma(connection(), m_crtc, gamma.size(), gamma.red(), + gamma.green(), gamma.blue()); + + return true; +} + +void X11Output::setCrtc(xcb_randr_crtc_t crtc) +{ + m_crtc = crtc; +} + +void X11Output::setGammaRampSize(int size) +{ + m_gammaRampSize = size; +} + } diff --git a/plugins/platforms/x11/standalone/x11_output.h b/plugins/platforms/x11/standalone/x11_output.h index d5f094058..944c123aa 100644 --- a/plugins/platforms/x11/standalone/x11_output.h +++ b/plugins/platforms/x11/standalone/x11_output.h @@ -1,64 +1,80 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_X11_OUTPUT_H #define KWIN_X11_OUTPUT_H #include "abstract_output.h" #include #include #include +#include + namespace KWin { /** * X11 output representation **/ class KWIN_EXPORT X11Output : public AbstractOutput { Q_OBJECT + public: explicit X11Output(QObject *parent = nullptr); virtual ~X11Output() = default; QString name() const override; void setName(QString set); /** * The geometry of this output in global compositor co-ordinates (i.e scaled) **/ QRect geometry() const override; void setGeometry(QRect set); /** * Current refresh rate in 1/ms. **/ int refreshRate() const override; void setRefreshRate(int set); + /** + * The size of gamma lookup table. + **/ + int gammaRampSize() const override; + bool setGammaRamp(const GammaRamp &gamma) override; + private: + void setCrtc(xcb_randr_crtc_t crtc); + void setGammaRampSize(int size); + + xcb_randr_crtc_t m_crtc = XCB_NONE; QString m_name; QRect m_geometry; + int m_gammaRampSize; int m_refreshRate; + + friend class X11StandalonePlatform; }; } #endif diff --git a/plugins/platforms/x11/standalone/x11_platform.cpp b/plugins/platforms/x11/standalone/x11_platform.cpp index 215af1ca2..2ad2a3f0b 100644 --- a/plugins/platforms/x11/standalone/x11_platform.cpp +++ b/plugins/platforms/x11/standalone/x11_platform.cpp @@ -1,549 +1,561 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "x11_platform.h" #include "x11cursor.h" #include "edge.h" #include "sync_filter.h" #include "windowselector.h" #include #include #if HAVE_EPOXY_GLX #include "glxbackend.h" #endif #if HAVE_X11_XINPUT #include "xinputintegration.h" #endif #include "abstract_client.h" #include "effects_x11.h" #include "eglonxbackend.h" #include "keyboard_input.h" #include "logging.h" #include "screens_xrandr.h" #include "screenedges_filter.h" #include "options.h" #include "overlaywindow_x11.h" #include "non_composited_outline.h" #include "workspace.h" #include "x11_decoration_renderer.h" #include "x11_output.h" #include "xcbutils.h" #include #include #include #include #include #include #include namespace KWin { X11StandalonePlatform::X11StandalonePlatform(QObject *parent) : Platform(parent) , m_x11Display(QX11Info::display()) { #if HAVE_X11_XINPUT if (!qEnvironmentVariableIsSet("KWIN_NO_XI2")) { m_xinputIntegration = new XInputIntegration(m_x11Display, this); m_xinputIntegration->init(); if (!m_xinputIntegration->hasXinput()) { delete m_xinputIntegration; m_xinputIntegration = nullptr; } else { connect(kwinApp(), &Application::workspaceCreated, m_xinputIntegration, &XInputIntegration::startListening); } } #endif connect(kwinApp(), &Application::workspaceCreated, this, [this] { if (Xcb::Extensions::self()->isSyncAvailable()) { m_syncFilter = std::make_unique(); } } ); + + setSupportsGammaControl(true); } X11StandalonePlatform::~X11StandalonePlatform() { if (m_openGLFreezeProtectionThread) { m_openGLFreezeProtectionThread->quit(); m_openGLFreezeProtectionThread->wait(); delete m_openGLFreezeProtectionThread; } if (isReady()) { XRenderUtils::cleanup(); } } void X11StandalonePlatform::init() { if (!QX11Info::isPlatformX11()) { emit initFailed(); return; } XRenderUtils::init(kwinApp()->x11Connection(), kwinApp()->x11RootWindow()); setReady(true); emit screensQueried(); } Screens *X11StandalonePlatform::createScreens(QObject *parent) { return new XRandRScreens(this, parent); } OpenGLBackend *X11StandalonePlatform::createOpenGLBackend() { switch (options->glPlatformInterface()) { #if HAVE_EPOXY_GLX case GlxPlatformInterface: if (hasGlx()) { return new GlxBackend(m_x11Display); } else { qCWarning(KWIN_X11STANDALONE) << "Glx not available, trying EGL instead."; // no break, needs fall-through Q_FALLTHROUGH(); } #endif case EglPlatformInterface: return new EglOnXBackend(m_x11Display); default: // no backend available return nullptr; } } Edge *X11StandalonePlatform::createScreenEdge(ScreenEdges *edges) { if (m_screenEdgesFilter.isNull()) { m_screenEdgesFilter.reset(new ScreenEdgesFilter); } return new WindowBasedEdge(edges); } void X11StandalonePlatform::createPlatformCursor(QObject *parent) { auto c = new X11Cursor(parent, m_xinputIntegration != nullptr); #if HAVE_X11_XINPUT if (m_xinputIntegration) { m_xinputIntegration->setCursor(c); // we know we have xkb already auto xkb = input()->keyboard()->xkb(); xkb->setConfig(kwinApp()->kxkbConfig()); xkb->reconfigure(); } #endif } bool X11StandalonePlatform::requiresCompositing() const { return false; } bool X11StandalonePlatform::openGLCompositingIsBroken() const { const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); return KConfigGroup(kwinApp()->config(), "Compositing").readEntry(unsafeKey, false); } QString X11StandalonePlatform::compositingNotPossibleReason() const { // first off, check whether we figured that we'll crash on detection because of a buggy driver KConfigGroup gl_workaround_group(kwinApp()->config(), "Compositing"); const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); if (gl_workaround_group.readEntry("Backend", "OpenGL") == QLatin1String("OpenGL") && gl_workaround_group.readEntry(unsafeKey, false)) return i18n("OpenGL compositing (the default) has crashed KWin in the past.
" "This was most likely due to a driver bug." "

If you think that you have meanwhile upgraded to a stable driver,
" "you can reset this protection but be aware that this might result in an immediate crash!

" "

Alternatively, you might want to use the XRender backend instead.

"); if (!Xcb::Extensions::self()->isCompositeAvailable() || !Xcb::Extensions::self()->isDamageAvailable()) { return i18n("Required X extensions (XComposite and XDamage) are not available."); } #if !defined( KWIN_HAVE_XRENDER_COMPOSITING ) if (!hasGlx()) return i18n("GLX/OpenGL are not available and only OpenGL support is compiled."); #else if (!(hasGlx() || (Xcb::Extensions::self()->isRenderAvailable() && Xcb::Extensions::self()->isFixesAvailable()))) { return i18n("GLX/OpenGL and XRender/XFixes are not available."); } #endif return QString(); } bool X11StandalonePlatform::compositingPossible() const { // first off, check whether we figured that we'll crash on detection because of a buggy driver KConfigGroup gl_workaround_group(kwinApp()->config(), "Compositing"); const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); if (gl_workaround_group.readEntry("Backend", "OpenGL") == QLatin1String("OpenGL") && gl_workaround_group.readEntry(unsafeKey, false)) return false; if (!Xcb::Extensions::self()->isCompositeAvailable()) { qCDebug(KWIN_CORE) << "No composite extension available"; return false; } if (!Xcb::Extensions::self()->isDamageAvailable()) { qCDebug(KWIN_CORE) << "No damage extension available"; return false; } if (hasGlx()) return true; #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (Xcb::Extensions::self()->isRenderAvailable() && Xcb::Extensions::self()->isFixesAvailable()) return true; #endif if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) { return true; } else if (qstrcmp(qgetenv("KWIN_COMPOSE"), "O2ES") == 0) { return true; } qCDebug(KWIN_CORE) << "No OpenGL or XRender/XFixes support"; return false; } bool X11StandalonePlatform::hasGlx() { return Xcb::Extensions::self()->hasGlx(); } void X11StandalonePlatform::createOpenGLSafePoint(OpenGLSafePoint safePoint) { const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); auto group = KConfigGroup(kwinApp()->config(), "Compositing"); switch (safePoint) { case OpenGLSafePoint::PreInit: group.writeEntry(unsafeKey, true); group.sync(); // Deliberately continue with PreFrame Q_FALLTHROUGH(); case OpenGLSafePoint::PreFrame: if (m_openGLFreezeProtectionThread == nullptr) { Q_ASSERT(m_openGLFreezeProtection == nullptr); m_openGLFreezeProtectionThread = new QThread(this); m_openGLFreezeProtectionThread->setObjectName("FreezeDetector"); m_openGLFreezeProtectionThread->start(); m_openGLFreezeProtection = new QTimer; m_openGLFreezeProtection->setInterval(15000); m_openGLFreezeProtection->setSingleShot(true); m_openGLFreezeProtection->start(); const QString configName = kwinApp()->config()->name(); m_openGLFreezeProtection->moveToThread(m_openGLFreezeProtectionThread); connect(m_openGLFreezeProtection, &QTimer::timeout, m_openGLFreezeProtection, [configName] { const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); auto group = KConfigGroup(KSharedConfig::openConfig(configName), "Compositing"); group.writeEntry(unsafeKey, true); group.sync(); KCrash::setDrKonqiEnabled(false); qFatal("Freeze in OpenGL initialization detected"); }, Qt::DirectConnection); } else { Q_ASSERT(m_openGLFreezeProtection); QMetaObject::invokeMethod(m_openGLFreezeProtection, "start", Qt::QueuedConnection); } break; case OpenGLSafePoint::PostInit: group.writeEntry(unsafeKey, false); group.sync(); // Deliberately continue with PostFrame Q_FALLTHROUGH(); case OpenGLSafePoint::PostFrame: QMetaObject::invokeMethod(m_openGLFreezeProtection, "stop", Qt::QueuedConnection); break; case OpenGLSafePoint::PostLastGuardedFrame: m_openGLFreezeProtection->deleteLater(); m_openGLFreezeProtection = nullptr; m_openGLFreezeProtectionThread->quit(); m_openGLFreezeProtectionThread->wait(); delete m_openGLFreezeProtectionThread; m_openGLFreezeProtectionThread = nullptr; break; } } PlatformCursorImage X11StandalonePlatform::cursorImage() const { auto c = kwinApp()->x11Connection(); QScopedPointer cursor( xcb_xfixes_get_cursor_image_reply(c, xcb_xfixes_get_cursor_image_unchecked(c), nullptr)); if (cursor.isNull()) { return PlatformCursorImage(); } QImage qcursorimg((uchar *) xcb_xfixes_get_cursor_image_cursor_image(cursor.data()), cursor->width, cursor->height, QImage::Format_ARGB32_Premultiplied); // deep copy of image as the data is going to be freed return PlatformCursorImage(qcursorimg.copy(), QPoint(cursor->xhot, cursor->yhot)); } void X11StandalonePlatform::doHideCursor() { xcb_xfixes_hide_cursor(kwinApp()->x11Connection(), kwinApp()->x11RootWindow()); } void X11StandalonePlatform::doShowCursor() { xcb_xfixes_show_cursor(kwinApp()->x11Connection(), kwinApp()->x11RootWindow()); } void X11StandalonePlatform::startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName) { if (m_windowSelector.isNull()) { m_windowSelector.reset(new WindowSelector); } m_windowSelector->start(callback, cursorName); } void X11StandalonePlatform::startInteractivePositionSelection(std::function callback) { if (m_windowSelector.isNull()) { m_windowSelector.reset(new WindowSelector); } m_windowSelector->start(callback); } void X11StandalonePlatform::setupActionForGlobalAccel(QAction *action) { connect(action, &QAction::triggered, kwinApp(), [action] { QVariant timestamp = action->property("org.kde.kglobalaccel.activationTimestamp"); bool ok = false; const quint32 t = timestamp.toULongLong(&ok); if (ok) { kwinApp()->setX11Time(t); } }); } OverlayWindow *X11StandalonePlatform::createOverlayWindow() { return new OverlayWindowX11(); } /* Updates xTime(). This used to simply fetch current timestamp from the server, but that can cause xTime() to be newer than timestamp of events that are still in our events queue, thus e.g. making XSetInputFocus() caused by such event to be ignored. Therefore events queue is searched for first event with timestamp, and extra PropertyNotify is generated in order to make sure such event is found. */ void X11StandalonePlatform::updateXTime() { // NOTE: QX11Info::getTimestamp does not yet search the event queue as the old // solution did. This means there might be regressions currently. See the // documentation above on how it should be done properly. kwinApp()->setX11Time(QX11Info::getTimestamp(), Application::TimestampUpdate::Always); } OutlineVisual *X11StandalonePlatform::createOutline(Outline *outline) { // first try composited Outline auto ret = Platform::createOutline(outline); if (!ret) { ret = new NonCompositedOutlineVisual(outline); } return ret; } Decoration::Renderer *X11StandalonePlatform::createDecorationRenderer(Decoration::DecoratedClientImpl *client) { auto renderer = Platform::createDecorationRenderer(client); if (!renderer) { renderer = new Decoration::X11Renderer(client); } return renderer; } void X11StandalonePlatform::invertScreen() { using namespace Xcb::RandR; bool succeeded = false; if (Xcb::Extensions::self()->isRandrAvailable()) { const auto active_client = workspace()->activeClient(); ScreenResources res((active_client && active_client->window() != XCB_WINDOW_NONE) ? active_client->window() : rootWindow()); if (!res.isNull()) { for (int j = 0; j < res->num_crtcs; ++j) { auto crtc = res.crtcs()[j]; CrtcGamma gamma(crtc); if (gamma.isNull()) { continue; } if (gamma->size) { qCDebug(KWIN_CORE) << "inverting screen using xcb_randr_set_crtc_gamma"; const int half = gamma->size / 2 + 1; uint16_t *red = gamma.red(); uint16_t *green = gamma.green(); uint16_t *blue = gamma.blue(); for (int i = 0; i < half; ++i) { auto invert = [&gamma, i](uint16_t *ramp) { qSwap(ramp[i], ramp[gamma->size - 1 - i]); }; invert(red); invert(green); invert(blue); } xcb_randr_set_crtc_gamma(connection(), crtc, gamma->size, red, green, blue); succeeded = true; } } } } if (!succeeded) { Platform::invertScreen(); } } void X11StandalonePlatform::createEffectsHandler(Compositor *compositor, Scene *scene) { new EffectsHandlerImplX11(compositor, scene); } QVector X11StandalonePlatform::supportedCompositors() const { QVector compositors; #if HAVE_EPOXY_GLX compositors << OpenGLCompositing; #endif #ifdef KWIN_HAVE_XRENDER_COMPOSITING compositors << XRenderCompositing; #endif compositors << NoCompositing; return compositors; } void X11StandalonePlatform::initOutputs() { doUpdateOutputs(); } void X11StandalonePlatform::updateOutputs() { doUpdateOutputs(); } template void X11StandalonePlatform::doUpdateOutputs() { auto fallback = [this]() { auto *o = new X11Output(this); + o->setGammaRampSize(0); o->setRefreshRate(-1.0f); o->setName(QStringLiteral("Xinerama")); m_outputs << o; }; // TODO: instead of resetting all outputs, check if new output is added/removed // or still available and leave still available outputs in m_outputs // untouched (like in DRM backend) qDeleteAll(m_outputs); m_outputs.clear(); if (!Xcb::Extensions::self()->isRandrAvailable()) { fallback(); return; } T resources(rootWindow()); if (resources.isNull()) { fallback(); return; } xcb_randr_crtc_t *crtcs = resources.crtcs(); xcb_randr_mode_info_t *modes = resources.modes(); QVector infos(resources->num_crtcs); for (int i = 0; i < resources->num_crtcs; ++i) { infos[i] = Xcb::RandR::CrtcInfo(crtcs[i], resources->config_timestamp); } for (int i = 0; i < resources->num_crtcs; ++i) { Xcb::RandR::CrtcInfo info(infos.at(i)); xcb_randr_output_t *outputs = info.outputs(); QVector outputInfos(outputs ? resources->num_outputs : 0); if (outputs) { for (int i = 0; i < resources->num_outputs; ++i) { outputInfos[i] = Xcb::RandR::OutputInfo(outputs[i], resources->config_timestamp); } } float refreshRate = -1.0f; for (int j = 0; j < resources->num_modes; ++j) { if (info->mode == modes[j].id) { if (modes[j].htotal != 0 && modes[j].vtotal != 0) { // BUG 313996 // refresh rate calculation - WTF was wikipedia 1998 when I needed it? int dotclock = modes[j].dot_clock, vtotal = modes[j].vtotal; if (modes[j].mode_flags & XCB_RANDR_MODE_FLAG_INTERLACE) dotclock *= 2; if (modes[j].mode_flags & XCB_RANDR_MODE_FLAG_DOUBLE_SCAN) vtotal *= 2; refreshRate = dotclock/float(modes[j].htotal*vtotal); } break; // found mode } } const QRect geo = info.rect(); if (geo.isValid()) { + xcb_randr_crtc_t crtc = crtcs[i]; + + // TODO: Perhaps the output has to save the inherited gamma ramp and + // restore it during tear down. Currently neither standalone x11 nor + // drm platform do this. + Xcb::RandR::CrtcGamma gamma(crtc); + auto *o = new X11Output(this); + o->setCrtc(crtc); + o->setGammaRampSize(gamma.isNull() ? 0 : gamma->size); o->setGeometry(geo); o->setRefreshRate(refreshRate); QString name; for (int j = 0; j < info->num_outputs; ++j) { Xcb::RandR::OutputInfo outputInfo(outputInfos.at(j)); - if (crtcs[i] == outputInfo->crtc) { + if (crtc == outputInfo->crtc) { name = outputInfo.name(); break; } } o->setName(name); m_outputs << o; } } if (m_outputs.isEmpty()) { fallback(); } } Outputs X11StandalonePlatform::outputs() const { return m_outputs; } Outputs X11StandalonePlatform::enabledOutputs() const { return m_outputs; } }