File indexing completed on 2024-04-28 04:05:34
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"