File indexing completed on 2024-04-21 15:08:50

0001 /***************************************************************************
0002  *   Copyright (C) 2012-2016 by Daniel Nicoletti <dantti12@gmail.com>      *
0003  *                                                                         *
0004  *   This program is free software; you can redistribute it and/or modify  *
0005  *   it under the terms of the GNU General Public License as published by  *
0006  *   the Free Software Foundation; either version 2 of the License, or     *
0007  *   (at your option) any later version.                                   *
0008  *                                                                         *
0009  *   This program is distributed in the hope that it will be useful,       *
0010  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0011  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0012  *   GNU General Public License for more details.                          *
0013  *                                                                         *
0014  *   You should have received a copy of the GNU General Public License     *
0015  *   along with this program; see the file COPYING. If not, write to       *
0016  *   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,  *
0017  *   Boston, MA 02110-1301, USA.                                           *
0018  ***************************************************************************/
0019 
0020 #include "ColorD.h"
0021 
0022 #include "DmiUtils.h"
0023 #include "ProfilesWatcher.h"
0024 #include "XEventHandler.h"
0025 
0026 #include "CdDeviceInterface.h"
0027 #include "CdInterface.h"
0028 #include "CdProfileInterface.h"
0029 
0030 #include <lcms2.h>
0031 
0032 #include <QApplication>
0033 #include <QDBusConnection>
0034 #include <QDBusMetaType>
0035 #include <QDBusReply>
0036 #include <QDBusServiceWatcher>
0037 #include <QDBusUnixFileDescriptor>
0038 #include <QFile>
0039 #include <QLoggingCategory>
0040 
0041 #include <KPluginFactory>
0042 
0043 K_PLUGIN_FACTORY_WITH_JSON(ColorDFactory, "colord.json", registerPlugin<ColorD>();)
0044 
0045 Q_LOGGING_CATEGORY(COLORD, "colord")
0046 
0047 typedef QList<QDBusObjectPath> ObjectPathList;
0048 
0049 ColorD::ColorD(QObject *parent, const QVariantList &)
0050     : KDEDModule(parent)
0051 {
0052     if (QApplication::platformName() != QLatin1String("xcb")) {
0053         // Wayland is not supported
0054         qCInfo(COLORD, "X11 not detect disabling");
0055         return;
0056     }
0057 
0058     // Register this first or the first time will fail
0059     qRegisterMetaType<CdStringMap>();
0060     qDBusRegisterMetaType<CdStringMap>();
0061     qDBusRegisterMetaType<QDBusUnixFileDescriptor>();
0062     qDBusRegisterMetaType<ObjectPathList>();
0063     qRegisterMetaType<Edid>();
0064 
0065     // connect to colord using DBus
0066     connectToColorD();
0067 
0068     // Connect to the display
0069     if ((m_resources = connectToDisplay()) == nullptr) {
0070         qCWarning(COLORD) << "Failed to connect to DISPLAY and get the needed resources";
0071         return;
0072     }
0073 
0074     // Make sure we know is colord is running
0075     auto watcher =
0076         new QDBusServiceWatcher(QStringLiteral("org.freedesktop.ColorManager"), QDBusConnection::systemBus(), QDBusServiceWatcher::WatchForOwnerChange, this);
0077     connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &ColorD::serviceOwnerChanged);
0078 
0079     // Create the profiles watcher thread
0080     m_profilesWatcher = new ProfilesWatcher;
0081     m_profilesWatcher->start();
0082 
0083     // Check outputs add all active outputs, once profiles are ready
0084     connect(m_profilesWatcher, &ProfilesWatcher::scanFinished, this, &ColorD::checkOutputs, Qt::QueuedConnection);
0085 
0086     // init the settings
0087     init();
0088 }
0089 
0090 ColorD::~ColorD()
0091 {
0092     const auto connectedOutputs = m_connectedOutputs;
0093     for (const Output::Ptr &out : connectedOutputs) {
0094         removeOutput(out);
0095     }
0096 
0097     if (m_x11EventHandler) {
0098         m_x11EventHandler->deleteLater();
0099     }
0100 
0101     // Stop the thread
0102     if (m_profilesWatcher) {
0103         m_profilesWatcher->quit();
0104         m_profilesWatcher->wait();
0105         m_profilesWatcher->deleteLater();
0106     }
0107 }
0108 
0109 void ColorD::init()
0110 {
0111     // Scan all the *.icc files later on it's own thread as this takes quite some time
0112     QMetaObject::invokeMethod(m_profilesWatcher, "scanHomeDirectory", Qt::QueuedConnection);
0113 }
0114 
0115 void ColorD::reset()
0116 {
0117     m_connectedOutputs.clear();
0118 }
0119 
0120 void ColorD::addEdidProfileToDevice(const Output::Ptr &output)
0121 {
0122     // Ask for profiles
0123     // TODO do it async
0124     QDBusReply<ObjectPathList> paths = m_cdInterface->GetProfiles();
0125 
0126     // Search through all profiles to see if the edid md5 matches
0127     foreach (const QDBusObjectPath &profilePath, paths.value()) {
0128         const CdStringMap metadata = getProfileMetadata(profilePath);
0129         const auto data = metadata.constFind(QStringLiteral("EDID_md5"));
0130         if (data != metadata.constEnd() && data.value() == output->edidHash()) {
0131             qCDebug(COLORD) << "Found EDID profile for device" << profilePath.path() << output->name();
0132             if (output->interface()) {
0133                 output->interface()->AddProfile(QStringLiteral("soft"), profilePath);
0134             }
0135         }
0136     }
0137 }
0138 
0139 CdStringMap ColorD::getProfileMetadata(const QDBusObjectPath &profilePath)
0140 {
0141     CdProfileInterface profile(QStringLiteral("org.freedesktop.ColorManager"), profilePath.path(), QDBusConnection::systemBus());
0142     return profile.metadata();
0143 }
0144 
0145 void ColorD::serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner)
0146 {
0147     Q_UNUSED(serviceName)
0148     if (newOwner.isEmpty()) {
0149         // colord has quit
0150         reset();
0151     } else if (oldOwner != newOwner) {
0152         // colord has a new owner
0153         reset();
0154         init();
0155     } else {
0156         // colord has started
0157         init();
0158     }
0159 }
0160 
0161 void ColorD::addOutput(const Output::Ptr &output)
0162 {
0163     QString edidVendor = QStringLiteral("unknown");
0164     QString edidModel = edidVendor;
0165     QString edidSerial = edidVendor;
0166     QString deviceId = QStringLiteral("xrandr-unknown");
0167 
0168     // ensure the RROutput is active
0169     if (!output->isActive()) {
0170         qCDebug(COLORD) << "output is not active" << output->name();
0171         return;
0172     }
0173 
0174     // Check if the output is the laptop panel
0175     bool isLaptop = output->isLaptop();
0176 
0177     Edid edid = output->readEdidData();
0178     // If it's not the laptop panel and the edid is valid grab the edid info
0179     if (!isLaptop && edid.isValid()) {
0180         if (!edid.vendor().isEmpty()) {
0181             // Store the monitor vendor
0182             edidVendor = edid.vendor();
0183         }
0184         if (!edid.name().isEmpty()) {
0185             // Store the monitor model
0186             edidModel = edid.name();
0187         }
0188     } else if (isLaptop) {
0189         // Use the DMI info when on a laptop
0190         edidModel = DmiUtils::deviceModel();
0191         edidVendor = DmiUtils::deviceVendor();
0192     } else {
0193         // Fallback to the output name
0194         edidModel = output->name();
0195     }
0196 
0197     if (!edid.serial().isEmpty()) {
0198         // Store the EDID serial number
0199         edidSerial = edid.serial();
0200     }
0201 
0202     // grabing the device even if edid is not valid
0203     // if handles the fallback name if it's not valid
0204     deviceId = output->id();
0205 
0206     // Creates the default profile
0207     QMetaObject::invokeMethod(m_profilesWatcher, "createIccProfile", Qt::QueuedConnection, Q_ARG(bool, isLaptop), Q_ARG(Edid, edid));
0208 
0209     // build up a map with the output properties to send to colord
0210     CdStringMap properties;
0211     properties[CD_DEVICE_PROPERTY_KIND] = QStringLiteral("display");
0212     properties[CD_DEVICE_PROPERTY_MODE] = QStringLiteral("physical");
0213     properties[CD_DEVICE_PROPERTY_COLORSPACE] = QStringLiteral("rgb");
0214     properties[CD_DEVICE_PROPERTY_VENDOR] = edidVendor;
0215     properties[CD_DEVICE_PROPERTY_MODEL] = edidModel;
0216     properties[CD_DEVICE_PROPERTY_SERIAL] = edidSerial;
0217     properties[CD_DEVICE_METADATA_XRANDR_NAME] = output->name();
0218     if (output->isPrimary(m_has_1_3, m_root)) {
0219         properties[CD_DEVICE_METADATA_OUTPUT_PRIORITY] = CD_DEVICE_METADATA_OUTPUT_PRIORITY_PRIMARY;
0220     } else {
0221         properties[CD_DEVICE_METADATA_OUTPUT_PRIORITY] = CD_DEVICE_METADATA_OUTPUT_PRIORITY_PRIMARY;
0222     }
0223     properties[CD_DEVICE_METADATA_OUTPUT_EDID_MD5] = output->edidHash();
0224     properties[CD_DEVICE_PROPERTY_EMBEDDED] = QChar::fromLatin1(output->isLaptop());
0225 
0226     // We use temp because if we crash or quit the device gets removed
0227     qCDebug(COLORD) << "Adding device id" << deviceId;
0228     qCDebug(COLORD) << "Output Hash" << output->edidHash();
0229     qCDebug(COLORD) << "Output isLaptop" << output->isLaptop();
0230 
0231     // TODO do async
0232     QDBusReply<QDBusObjectPath> reply;
0233     reply = m_cdInterface->CreateDevice(deviceId, QStringLiteral("temp"), properties);
0234     if (reply.isValid()) {
0235         qCDebug(COLORD) << "Created colord device" << reply.value().path();
0236         // Store the output path into our Output class
0237         output->setPath(reply.value());
0238 
0239         // Store the active output into the connected list
0240         m_connectedOutputs << output;
0241 
0242         // Check if there is any EDID profile to be added
0243         addEdidProfileToDevice(output);
0244 
0245         // Make sure we set the profile on this device
0246         outputChanged(output);
0247     } else {
0248         qCWarning(COLORD) << "Failed to register device:" << reply.error().message();
0249         reply = m_cdInterface->FindDeviceById(deviceId);
0250         if (reply.isValid()) {
0251             qCDebug(COLORD) << "Found colord device" << reply.value().path();
0252 
0253             bool found = false;
0254             for (auto iter : std::as_const(m_connectedOutputs)) {
0255                 if (iter->id() == deviceId) {
0256                     found = true;
0257 
0258                     // Store the output path into our Output class
0259                     iter->setPath(reply.value());
0260 
0261                     // Check if there is any EDID profile to be added
0262                     addEdidProfileToDevice(iter);
0263 
0264                     // Make sure we set the profile on this device
0265                     outputChanged(iter);
0266                     break;
0267                 }
0268             }
0269 
0270             if (!found) {
0271                 qCDebug(COLORD) << "Failed to locate" << deviceId << "in the list of known outputs";
0272             }
0273         }
0274     }
0275 }
0276 
0277 int ColorD::getPrimaryCRTCId(XID primary) const
0278 {
0279     for (int crtc = 0; crtc < m_resources->ncrtc; crtc++) {
0280         XRRCrtcInfo *crtcInfo = XRRGetCrtcInfo(m_dpy, m_resources, m_resources->crtcs[crtc]);
0281         if (!crtcInfo) {
0282             continue;
0283         }
0284 
0285         if (crtcInfo->mode != None && crtcInfo->noutput > 0) {
0286             for (int output = 0; output < crtcInfo->noutput; output++) {
0287                 if (crtcInfo->outputs[output] == primary) {
0288                     return crtc;
0289                 }
0290             }
0291         }
0292         XRRFreeCrtcInfo(crtcInfo);
0293     }
0294 
0295     return -1;
0296 }
0297 
0298 QList<ColorD::X11Monitor> ColorD::getAtomIds() const
0299 {
0300     QList<ColorD::X11Monitor> monitorList;
0301 
0302     if (!m_resources) {
0303         return monitorList;
0304     }
0305 
0306     // see if there is a primary screen.
0307     const XID primary = XRRGetOutputPrimary(m_dpy, m_root);
0308     const int primaryId = getPrimaryCRTCId(primary);
0309     bool havePrimary = false;
0310     if (primaryId == -1) {
0311         qCDebug(COLORD) << "Couldn't locate primary CRTC.";
0312     } else {
0313         qCDebug(COLORD) << "Primary CRTC is at CRTC " << primaryId;
0314         havePrimary = true;
0315     }
0316 
0317     // now iterate over the CRTCs again and add the relevant ones to the list
0318     int atomId = 0; // the id of the x atom. might be changed when sorting in the primary later!
0319     for (int crtc = 0; crtc < m_resources->ncrtc; ++crtc) {
0320         XRROutputInfo *outputInfo = nullptr;
0321         XRRCrtcInfo *crtcInfo = XRRGetCrtcInfo(m_dpy, m_resources, m_resources->crtcs[crtc]);
0322         if (!crtcInfo) {
0323             qCDebug(COLORD) << "Can't get CRTC info for CRTC " << crtc;
0324             continue;
0325         }
0326         // only handle those that are attached though
0327         if (crtcInfo->mode == None || crtcInfo->noutput <= 0) {
0328             qCDebug(COLORD) << "CRTC for CRTC " << crtc << " has no mode or no output, skipping";
0329             XRRFreeCrtcInfo(crtcInfo);
0330             continue;
0331         }
0332 
0333         // Choose the primary output of the CRTC if we have one, else default to the first. i.e. we punt with
0334         // mirrored displays.
0335         bool isPrimary = false;
0336         int output = 0;
0337         if (havePrimary) {
0338             for (int j = 0; j < crtcInfo->noutput; j++) {
0339                 if (crtcInfo->outputs[j] == primary) {
0340                     output = j;
0341                     isPrimary = true;
0342                     break;
0343                 }
0344             }
0345         }
0346 
0347         outputInfo = XRRGetOutputInfo(m_dpy, m_resources, crtcInfo->outputs[output]);
0348         if (!outputInfo) {
0349             qCDebug(COLORD) << "Can't get output info for CRTC " << crtc << " output " << output;
0350             XRRFreeCrtcInfo(crtcInfo);
0351             XRRFreeOutputInfo(outputInfo);
0352             continue;
0353         }
0354 
0355         if (outputInfo->connection == RR_Disconnected) {
0356             qCDebug(COLORD) << "CRTC " << crtc << " output " << output << " is disconnected, skipping";
0357             XRRFreeCrtcInfo(crtcInfo);
0358             XRRFreeOutputInfo(outputInfo);
0359             continue;
0360         }
0361 
0362         ColorD::X11Monitor monitor;
0363 
0364         monitor.crtc = m_resources->crtcs[crtc];
0365         monitor.isPrimary = isPrimary;
0366         monitor.atomId = atomId++;
0367         monitor.name = outputInfo->name;
0368         monitorList.append(monitor);
0369 
0370         XRRFreeCrtcInfo(crtcInfo);
0371         XRRFreeOutputInfo(outputInfo);
0372     }
0373 
0374     // sort the list of monitors so that the primary one is first. also updates the atomId.
0375     struct {
0376         bool operator()(const ColorD::X11Monitor &monitorA, const ColorD::X11Monitor &monitorB) const
0377         {
0378             if (monitorA.isPrimary)
0379                 return true;
0380             if (monitorB.isPrimary)
0381                 return false;
0382 
0383             return monitorA.atomId < monitorB.atomId;
0384         }
0385     } sortMonitorList;
0386     std::sort(monitorList.begin(), monitorList.end(), sortMonitorList);
0387     atomId = 0;
0388     for (auto monitor : std::as_const(monitorList)) {
0389         monitor.atomId = atomId++;
0390     }
0391 
0392     return monitorList;
0393 }
0394 
0395 void ColorD::outputChanged(const Output::Ptr &output)
0396 {
0397     qCDebug(COLORD) << "Device changed" << output->path().path();
0398 
0399     if (!output->interface()) {
0400         return;
0401     }
0402 
0403     // check Device.Kind is "display"
0404     if (output->interface()->kind() != QLatin1String("display")) {
0405         // not a display device, ignoring
0406         qCDebug(COLORD) << "Not a display device, ignoring" << output->name() << output->interface()->kind();
0407         return;
0408     }
0409 
0410     QList<QDBusObjectPath> profiles = output->interface()->profiles();
0411     if (profiles.isEmpty()) {
0412         // There are no profiles ignoring
0413         qCDebug(COLORD) << "There are no profiles, ignoring" << output->name();
0414         return;
0415     }
0416 
0417     // read the default profile (the first path in the Device.Profiles property)
0418     QDBusObjectPath profileDefault = profiles.first();
0419     qCDebug(COLORD) << "profileDefault" << profileDefault.path();
0420     CdProfileInterface profile(QStringLiteral("org.freedesktop.ColorManager"), profileDefault.path(), QDBusConnection::systemBus());
0421     if (!profile.isValid()) {
0422         qCDebug(COLORD) << "Profile invalid" << output->name() << profile.lastError();
0423         return;
0424     }
0425     QString filename = profile.filename();
0426     qCDebug(COLORD) << "Default Profile Filename" << output->name() << filename;
0427 
0428     QFile file(filename);
0429     QByteArray data;
0430     if (file.open(QIODevice::ReadOnly)) {
0431         data = file.readAll();
0432     } else {
0433         qCWarning(COLORD) << "Failed to open profile" << output->name() << filename;
0434         return;
0435     }
0436 
0437     // read the VCGT data using lcms2
0438     const cmsToneCurve **vcgt;
0439     cmsHPROFILE lcms_profile = nullptr;
0440 
0441     // open file
0442     lcms_profile = cmsOpenProfileFromMem((const uint *)data.data(), data.size());
0443     if (lcms_profile == nullptr) {
0444         qCWarning(COLORD) << "Could not open profile with lcms" << output->name() << filename;
0445         return;
0446     }
0447 
0448     // The gamma size of this output
0449     int gammaSize = output->getGammaSize();
0450     if (gammaSize == 0) {
0451         qCWarning(COLORD) << "Gamma size is zero" << output->name();
0452         cmsCloseProfile(lcms_profile);
0453         return;
0454     }
0455 
0456     // Allocate the gamma
0457     XRRCrtcGamma *gamma = XRRAllocGamma(gammaSize);
0458 
0459     // get tone curves from profile
0460     vcgt = static_cast<const cmsToneCurve **>(cmsReadTag(lcms_profile, cmsSigVcgtTag));
0461     if (vcgt == nullptr || vcgt[0] == nullptr) {
0462         qCDebug(COLORD) << "Profile does not have any VCGT data, reseting" << output->name() << filename;
0463         // Reset the gamma table
0464         for (int i = 0; i < gammaSize; ++i) {
0465             uint value = (i * 0xffff) / (gammaSize - 1);
0466             gamma->red[i] = value;
0467             gamma->green[i] = value;
0468             gamma->blue[i] = value;
0469         }
0470     } else {
0471         // Fill the gamma table with the VCGT data
0472         for (int i = 0; i < gammaSize; ++i) {
0473             cmsFloat32Number in;
0474             in = (double)i / (double)(gammaSize - 1);
0475             gamma->red[i] = cmsEvalToneCurveFloat(vcgt[0], in) * (double)0xffff;
0476             gamma->green[i] = cmsEvalToneCurveFloat(vcgt[1], in) * (double)0xffff;
0477             gamma->blue[i] = cmsEvalToneCurveFloat(vcgt[2], in) * (double)0xffff;
0478         }
0479     }
0480     cmsCloseProfile(lcms_profile);
0481 
0482     // push the data to the Xrandr gamma ramps for the display
0483     output->setGamma(gamma);
0484 
0485     XRRFreeGamma(gamma);
0486 
0487     // export the file data as an x atom
0488     // during startup the order of outputs can change, so caching the atomId doesn't work that great
0489     int atomId = -1;
0490     const QList<ColorD::X11Monitor> monitorList = getAtomIds();
0491     for (const auto &monitor : monitorList) {
0492         if (monitor.crtc == output->crtc()) {
0493             atomId = monitor.atomId;
0494             break;
0495         }
0496     }
0497     if (atomId >= 0) {
0498         QString atomString = QStringLiteral("_ICC_PROFILE");
0499         if (atomId > 0) {
0500             atomString.append(QStringLiteral("_%1").arg(atomId));
0501         }
0502         qCInfo(COLORD) << "Setting X atom (id:" << atomId << ")" << atomString << "on output:" << output->name();
0503         QByteArray atomBytes = atomString.toLatin1();
0504         const char *atomChars = atomBytes.constData();
0505         Atom prop = XInternAtom(m_dpy, atomChars, false);
0506         int rc = XChangeProperty(m_dpy, m_root, prop, XA_CARDINAL, 8, PropModeReplace, (unsigned char *)data.data(), data.size());
0507 
0508         // for some reason this fails with BadRequest, but actually sets the value
0509         if (rc != BadRequest && rc != Success) {
0510             qCWarning(COLORD) << "Failed to set XProperty";
0511         }
0512     } else {
0513         qCDebug(COLORD) << "Failed to get an atomId for" << output->name();
0514     }
0515 }
0516 
0517 void ColorD::removeOutput(const Output::Ptr &output)
0518 {
0519     /* call DBus DeleteDevice() on the output */
0520     m_cdInterface->DeleteDevice(output->path());
0521 
0522     // Remove the output from the connected list
0523     m_connectedOutputs.removeOne(output);
0524 }
0525 
0526 XRRScreenResources *ColorD::connectToDisplay()
0527 {
0528     m_dpy = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->display();
0529 
0530     // Check extension
0531     int eventBase;
0532     int major_version, minor_version;
0533     if (!XRRQueryExtension(m_dpy, &eventBase, &m_errorBase) || !XRRQueryVersion(m_dpy, &major_version, &minor_version)) {
0534         qCWarning(COLORD) << "RandR extension missing";
0535         return nullptr;
0536     }
0537 
0538     // Install our X event handler
0539     m_x11EventHandler = new XEventHandler(eventBase);
0540     connect(m_x11EventHandler, SIGNAL(outputChanged()), this, SLOT(checkOutputs()));
0541 
0542     // check if we have the new version of the XRandR extension
0543     bool has_1_2;
0544     has_1_2 = (major_version > 1 || (major_version == 1 && minor_version >= 2));
0545     m_has_1_3 = (major_version > 1 || (major_version == 1 && minor_version >= 3));
0546 
0547     if (m_has_1_3) {
0548         qCDebug(COLORD) << "Using XRANDR extension 1.3 or greater.";
0549     } else if (has_1_2) {
0550         qCDebug(COLORD) << "Using XRANDR extension 1.2.";
0551     } else {
0552         qCDebug(COLORD) << "Using legacy XRANDR extension (1.1 or earlier).";
0553     }
0554 
0555     m_root = RootWindow(m_dpy, 0);
0556 
0557     // This call is CRITICAL:
0558     // RR 1.3 has a new method called XRRGetScreenResourcesCurrent,
0559     // that method is less expensive since it only gets the current
0560     // cached values from X. On the other hand in the case of
0561     // a session startup the EDID of the output is not cached, leading
0562     // to our code failing to get a valid EDID. This call ensures the
0563     // X server will probe all outputs again and cache the EDID.
0564     // Note: This code only runs once so it's nothing that would
0565     // actually slow things down.
0566     return XRRGetScreenResources(m_dpy, m_root);
0567 }
0568 
0569 void ColorD::checkOutputs()
0570 {
0571     qCDebug(COLORD) << "Checking outputs";
0572     // Check the output as something has changed
0573     for (int i = 0; i < m_resources->noutput; ++i) {
0574         bool found = false;
0575         Output::Ptr currentOutput(new Output(m_resources->outputs[i], m_resources));
0576         foreach (const Output::Ptr &output, m_connectedOutputs) {
0577             if (output->output() == m_resources->outputs[i]) {
0578                 if (!currentOutput->isActive()) {
0579                     // The device is not active anymore
0580                     qCDebug(COLORD) << "remove device";
0581                     removeOutput(output);
0582                     found = true;
0583                     break;
0584                 }
0585             }
0586         }
0587 
0588         if (!found && currentOutput->isActive()) {
0589             // Output is now connected and active
0590             addOutput(currentOutput);
0591         }
0592     }
0593 }
0594 
0595 void ColorD::profileAdded(const QDBusObjectPath &profilePath)
0596 {
0597     // check if the EDID_md5 Profile.Metadata matches any active
0598     // XRandR devices (e.g. lvds1), otherwise ignore
0599     const CdStringMap metadata = getProfileMetadata(profilePath);
0600     const auto data = metadata.constFind(QStringLiteral("EDID_md5"));
0601     if (data != metadata.constEnd()) {
0602         const QString edidHash = data.value();
0603         Output::Ptr output;
0604         // Get the Crtc of this output
0605         for (int i = 0; i < m_connectedOutputs.size(); ++i) {
0606             if (m_connectedOutputs.at(i)->edidHash() == edidHash) {
0607                 output = m_connectedOutputs[i];
0608                 break;
0609             }
0610         }
0611 
0612         if (output && output->interface()) {
0613             // Found an EDID that matches the md5
0614             output->interface()->AddProfile(QStringLiteral("soft"), profilePath);
0615         }
0616     }
0617 }
0618 
0619 void ColorD::deviceAdded(const QDBusObjectPath &objectPath)
0620 {
0621     qCDebug(COLORD) << "Device added" << objectPath.path();
0622     //    QDBusInterface deviceInterface(QLatin1String("org.freedesktop.ColorManager"),
0623     //                                   objectPath.path(),
0624     //                                   QLatin1String("org.freedesktop.ColorManager.Device"),
0625     //                                   QDBusConnection::systemBus(),
0626     //                                   this);
0627     //    if (!deviceInterface.isValid()) {
0628     //        return;
0629     //    }
0630 
0631     //    // check Device.Kind is "display"
0632     //    if (deviceInterface.property("Kind").toString() != QLatin1String("display")) {
0633     //        // not a display device, ignoring
0634     //        return;
0635     //    }
0636 
0637     /* show a notification if the user should calibrate the device */
0638     // TODO
0639 }
0640 
0641 void ColorD::deviceChanged(const QDBusObjectPath &objectPath)
0642 {
0643     qCDebug(COLORD) << "Device changed" << objectPath.path();
0644     Output::Ptr output;
0645     // Get the Crtc of this output
0646     for (int i = 0; i < m_connectedOutputs.size(); ++i) {
0647         if (m_connectedOutputs.at(i)->path() == objectPath) {
0648             output = m_connectedOutputs[i];
0649             break;
0650         }
0651     }
0652 
0653     if (output.isNull()) {
0654         qCWarning(COLORD) << "Output not found";
0655         return;
0656     }
0657 
0658     outputChanged(output);
0659 }
0660 
0661 void ColorD::connectToColorD()
0662 {
0663     // Creates a ColorD interface, it must be created with new
0664     // otherwise the object will be deleted when this block ends
0665     m_cdInterface =
0666         new CdInterface(QStringLiteral("org.freedesktop.ColorManager"), QStringLiteral("/org/freedesktop/ColorManager"), QDBusConnection::systemBus(), this);
0667 
0668     // listen to colord for events
0669     connect(m_cdInterface, &CdInterface::ProfileAdded, this, &ColorD::profileAdded);
0670     connect(m_cdInterface, &CdInterface::DeviceAdded, this, &ColorD::deviceAdded);
0671     connect(m_cdInterface, &CdInterface::DeviceChanged, this, &ColorD::deviceChanged);
0672 }
0673 
0674 #include "ColorD.moc"
0675 
0676 #include "moc_ColorD.cpp"