File indexing completed on 2022-09-27 13:43:49

0001 /********************************************************************
0002  KolorServer - color server based on the X Color Management Specification
0003  This file is part of the KDE project.
0004 
0005 Copyright (C) 2012 Casian Andrei <skeletk13@gmail.com>
0006 
0007 Redistribution and use in source and binary forms, with or without
0008 modification, are permitted provided that the following conditions
0009 are met:
0010 
0011 1. Redistributions of source code must retain the above copyright
0012    notice, this list of conditions and the following disclaimer.
0013 2. Redistributions in binary form must reproduce the above copyright
0014    notice, this list of conditions and the following disclaimer in the
0015    documentation and/or other materials provided with the distribution.
0016 
0017 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
0018 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0019 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
0020 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
0021 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
0022 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
0023 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
0024 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0025 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
0026 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0027 *********************************************************************/
0028 
0029 #include <KDebug>
0030 #include <QTimer>
0031 
0032 #include <unistd.h>
0033 
0034 #include "color-lookup-table.h"
0035 #include "screen.h"
0036 
0037 #include "display.h"
0038 
0039 #include <oyranos_devices.h>
0040 #include <X11/Xcm/XcmEvents.h>
0041 
0042 namespace KolorServer
0043 {
0044 
0045 static const int X11_EVENTS_POLL_INTERVAL         = 4000; //ms
0046 
0047 /*
0048  * Display
0049  */
0050 
0051 Display *s_display = 0;
0052 
0053 Display *Display::getInstance()
0054 {
0055     if (!s_display) {
0056         s_display = new Display;
0057         s_display->initialize();
0058     }
0059     return s_display;
0060 }
0061 
0062 void Display::cleanup()
0063 {
0064     delete s_display;
0065 }
0066 
0067 Display::Display()
0068     : m_screen(0)
0069     , m_xcmeContext(0)
0070     , m_colorDesktopActivated(true)
0071     , m_oyCache(0)
0072 {
0073     // Determine the display name
0074     QByteArray displayName = qgetenv("DISPLAY");
0075     if (displayName.isEmpty())
0076         displayName = ":0";
0077 
0078     // Open connection to the X Server
0079     m_display = X11::XOpenDisplay(displayName.constData());
0080     if (!m_display) {
0081         kFatal() << "Cannot connect to X server" << displayName;
0082         activateColorDesktop(false);
0083         return;
0084     }
0085 }
0086 
0087 Display::~Display()
0088 {
0089     if (!m_display)
0090         return;
0091 
0092     // Remove desktop colour management service mark
0093     X11::changeProperty(m_display, iccColorDesktop, XA_STRING, (unsigned char*) NULL, 0);
0094 
0095     // Delete default screen
0096     delete m_screen;
0097 
0098     // Uninit X11 Events monitoring
0099     if (m_xcmeContext)
0100         X11::XcmeContext_Release(&m_xcmeContext);
0101 
0102     // Delete cache
0103     oyStructList_Clear(m_oyCache);
0104     oyStructList_Release(&m_oyCache);
0105 
0106     // Close connection to the X Sever
0107     X11::XCloseDisplay(m_display);
0108 }
0109 
0110 void Display::initialize()
0111 {
0112     // Setup X11 event monitoring
0113     m_xcmeContext = X11::XcmeContext_New();
0114     if (m_xcmeContext) {
0115         X11::XcmeContext_DisplaySet(m_xcmeContext, m_display);
0116         if (X11::XcmeContext_Setup(m_xcmeContext, ""))
0117             kWarning() << "Unable to setup X11 event monitor";
0118     } else
0119         kWarning() << "Unable to create X11 event monitor";
0120 
0121     // Setup a timer for polling for X11 events
0122     QTimer *eventTimer = new QTimer(this);
0123     connect(eventTimer, SIGNAL(timeout()), this, SLOT(checkX11Events()));
0124     eventTimer->start(X11_EVENTS_POLL_INTERVAL);
0125 
0126     iccColorProfiles    = X11::XInternAtom(m_display, XCM_COLOR_PROFILES, False);
0127     iccColorRegions     = X11::XInternAtom(m_display, XCM_COLOR_REGIONS, False);
0128     iccColorOutputs     = X11::XInternAtom(m_display, XCM_COLOR_OUTPUTS, False);
0129     iccColorDesktop     = X11::XInternAtom(m_display, XCM_COLOR_DESKTOP, False);
0130     netDesktopGeometry  = X11::XInternAtom(m_display, "_NET_DESKTOP_GEOMETRY", False);
0131     iccDisplayAdvanced  = X11::XInternAtom(m_display, XCM_COLOUR_DESKTOP_ADVANCED, False);
0132 
0133     // Get the default screen
0134     int screenNumber = X11::defaultScreen(m_display);
0135     m_screen = new Screen(m_display, screenNumber, this);
0136     m_screen->setupOutputs();
0137     m_screen->updateOutputConfiguration(true);
0138 
0139     // Initialize Oyranos Cache
0140     m_oyCache = oyStructList_New(0);
0141 }
0142 
0143 Screen * Display::screen() const
0144 {
0145     return m_screen;
0146 }
0147 
0148 oyStructList_s* Display::cache()
0149 {
0150     return m_oyCache;
0151 }
0152 
0153 bool Display::colorDesktopActivated() const
0154 {
0155     return m_colorDesktopActivated;
0156 }
0157 
0158 void Display::activateColorDesktop (bool activate)
0159 {
0160     m_colorDesktopActivated = activate;
0161 }
0162 
0163 void Display::clean()
0164 {
0165     kDebug();
0166 
0167     int error;
0168     oyOptions_s *options = 0;
0169     oyConfigs_s *devices = 0;
0170 
0171     // Get display name
0172     QByteArray displayName = X11::XDisplayString(m_display);
0173     QByteArray t;
0174     if (displayName.contains('.'))
0175         displayName.resize(displayName.indexOf('.'));
0176     if (displayName.isEmpty())
0177         displayName = ":0";
0178 
0179     // Clean up old displays
0180     error = oyOptions_SetFromText(&options, "//"OY_TYPE_STD"/config/command", "unset", OY_CREATE_NEW);
0181     for (int screen = 0; screen < 200; ++screen)
0182     {
0183         QByteArray screenName = displayName + "." + QByteArray::number(screen);
0184         error = oyOptions_SetFromText(&options, "//"OY_TYPE_STD"/config/device_name",
0185                                       screenName.data(), OY_CREATE_NEW);
0186         error = oyDevicesGet(OY_TYPE_STD, "monitor", options, &devices);
0187         if (error)
0188             break;
0189         oyConfigs_Release(&devices);
0190     }
0191     oyOptions_Release(&options);
0192 
0193 
0194     /* get number of connected devices */
0195     error = oyOptions_SetFromText(&options, "//"OY_TYPE_STD"/config/command", "list", OY_CREATE_NEW);
0196     error = oyOptions_SetFromText(&options, "//"OY_TYPE_STD"/config/display_name", displayName.constData(), OY_CREATE_NEW);
0197     error = oyDevicesGet(OY_TYPE_STD, "monitor", options, &devices);
0198     oyConfigs_Release(&devices);
0199     oyOptions_Release(&options);
0200 
0201     /*
0202      * Monitor hotplugs can easily mess up the ICC profile to device assigment.
0203      * So first we erase the _ICC_PROFILE(_xxx) to get a clean state.
0204      * We setup the EDID atoms and ICC profiles new.
0205      * The ICC profiles are moved to the right places through the
0206      * PropertyChange events recieved by the colour server.
0207      */
0208 
0209     // Refresh EDID
0210     QByteArray screen0Name = displayName + ".0";
0211     error = oyOptions_SetFromText(&options, "//"OY_TYPE_STD"/config/command", "list", OY_CREATE_NEW);
0212     error = oyOptions_SetFromText(&options, "//"OY_TYPE_STD"/config/device_name", screen0Name.constData(), OY_CREATE_NEW);
0213     error = oyOptions_SetFromText(&options, "//"OY_TYPE_STD"/config/edid", "refresh", OY_CREATE_NEW);
0214     error = oyDevicesGet(OY_TYPE_STD, "monitor", options, &devices);
0215     oyConfigs_Release(&devices);
0216     oyOptions_Release(&options);
0217 }
0218 
0219 int Display::updateNetColorDesktopAtom(bool init)
0220 {
0221     static time_t lastUpdateTime = 0;
0222 
0223     enum status_e {
0224         statusOk                = 0,
0225         statusInactive          = 1,
0226         statusActive            = 2,
0227         statusError             = 3
0228     } status = statusOk;
0229     const QByteArray myID = "kolorserver";
0230     const QByteArray myCaps = "|ICM|V0.4|"; // TODO add other capabilities?
0231 
0232     if (!colorDesktopActivated())
0233         return (int) statusInactive;
0234 
0235     kDebug() << init;
0236 
0237     X11::Window rootWindow = X11::rootWindow(m_display, 0);
0238     unsigned long n = 0;
0239     char *data = (char*) X11::fetchProperty(m_display, rootWindow, iccColorDesktop, XA_STRING, &n, False);
0240 
0241     pid_t pid = getpid();
0242     const char *oldData = 0;
0243     int oldPid = 0;
0244     long atomTime = 0;
0245     time_t currentTime = time(NULL);
0246     QByteArray colorServerAtomName(1024, '\0');
0247     QByteArray capabilitiesAtomText(1024, '\0');
0248 
0249     if (n && data && strlen(data)) {
0250         sscanf((const char*) data, "%d %ld %s %s",
0251                &oldPid,
0252                &atomTime,
0253                capabilitiesAtomText.data(),
0254                colorServerAtomName.data());
0255         oldData = data;
0256     }
0257 
0258     const QByteArray otherSrv = oldData ? oldData : "????";
0259 
0260     if (n && data && oldPid != (int) pid) {
0261         if (oldData && currentTime - atomTime > 60) {
0262             kWarning() << "Found old _ICC_COLOR_DESKTOP:" << otherSrv;
0263             kWarning() << "Either there was a previous crash or your setup may be double color corrected";
0264         }
0265 
0266         // Check for taking over of colour service
0267         if (colorServerAtomName != myID) {
0268             if (atomTime < lastUpdateTime || init) {
0269                 kDebug() << "Taking over color service from old _ICC_COLOR_DESKTOP:" << otherSrv;
0270             } else {
0271                 if (atomTime > lastUpdateTime) {
0272                     kDebug() << "Giving color service to _ICC_COLOR_DESKTOP:" << otherSrv;
0273                     activateColorDesktop(false);
0274                 }
0275             }
0276         } else {
0277             kDebug() << "Taking over color service from old _ICC_COLOR_DESKTOP:" << otherSrv;
0278         }
0279     }
0280 
0281     if (lastUpdateTime - atomTime > 10 || init) {
0282         QByteArray newData(1024, '\0');
0283         snprintf(newData.data(), 1024, "%d %ld %s %s", (int) pid, (long) currentTime, myCaps.constData(), myID.constData());
0284 
0285         // Set the colour management desktop service activity atom
0286         if (m_screen->profileCount() > 0)
0287             X11::changeProperty(m_display, iccColorDesktop, XA_STRING, (const unsigned char*) newData.constData(), newData.size());
0288         else if (oldData) {
0289             /* switch off the plugin */
0290             X11::changeProperty(m_display, iccColorDesktop, XA_STRING, (const unsigned char*) NULL, 0);
0291             activateColorDesktop(false);
0292         }
0293     }
0294 
0295     lastUpdateTime = currentTime;
0296 
0297     return status;
0298 }
0299 
0300 bool Display::isAdvancedIccDisplay()
0301 {
0302     unsigned long nBytes;
0303     char *opt = 0;
0304     bool advanced = false;
0305     X11::Window rootWindow = X11::rootWindow(m_display, 0);
0306 
0307     // Optionally set advanced options from Oyranos
0308     opt = (char*) X11::fetchProperty(m_display, rootWindow, iccDisplayAdvanced, XA_STRING, &nBytes, False);
0309     kDebug() << "iccDisplayAdvanced, nBytes:" << nBytes;
0310     if (opt && nBytes && atoi(opt) > 0)
0311         advanced = atoi(opt) != 0;
0312     if (opt)
0313         X11::XFree(opt);
0314 
0315     return advanced;
0316 }
0317 
0318 void Display::handleEvent(X11::XEvent* event)
0319 {
0320     const char *atomName = 0;
0321 
0322     if (!colorDesktopActivated())
0323         return;
0324 
0325     switch (event->type) {
0326     case PropertyNotify:
0327         atomName = X11::XGetAtomName(event->xany.display, event->xproperty.atom);
0328 
0329         if (event->xproperty.atom == iccColorProfiles) {
0330             kDebug() << "ICC Color Profiles atom changed";
0331             m_screen->updateProfiles();
0332         } else if (event->xproperty.atom == iccColorRegions) {
0333             kDebug() << "ICC Color Regions atom changed";
0334             // CompWindow *w = findWindowAtDisplay(d, event->xproperty.window);
0335             // updateWindowRegions(w);
0336             // colour_desktop_region_count = -1;
0337             // TODO
0338         } else if (event->xproperty.atom == iccColorOutputs) {
0339             kDebug() << "ICC Color Outputs atom changed";
0340             // CompWindow *w = findWindowAtDisplay(d, event->xproperty.window);
0341             // updateWindowOutput(w);
0342             // TODO
0343         } else if (event->xproperty.atom == iccColorDesktop && atomName) {
0344             // Possibly let others take over the colour server
0345             kDebug() << "ICC Color Desktop atom changed";
0346             updateNetColorDesktopAtom(false);
0347         } else if (strstr(atomName, XCM_ICC_V0_3_TARGET_PROFILE_IN_X_BASE) != 0) {
0348             // Update for a changing monitor profile
0349             kDebug() << "ICC Output Profile atom changed";
0350             m_screen->updateProfileForAtom(atomName, event->xproperty.atom);
0351         } else if (event->xproperty.atom == netDesktopGeometry) {
0352             // Update for changing geometry
0353             kDebug() << "Desktop geometry atom changed";
0354             m_screen->setupOutputs();
0355             m_screen->updateOutputConfiguration(true);
0356         } else if (event->xproperty.atom == iccDisplayAdvanced) {
0357             kDebug() << "ICC Display Advanced atom changed";
0358             m_screen->updateOutputConfiguration(false);
0359         }
0360 
0361         break;
0362 
0363     case RRNotify: {
0364         X11::XRRNotifyEvent *rrn = (X11::XRRNotifyEvent *) event;
0365         if (rrn->subtype == RRNotify_OutputChange) {
0366             kDebug() << "XRandR outputs changed";
0367             m_screen->setupOutputs();
0368             m_screen->updateOutputConfiguration(true);
0369         }
0370         break;
0371     }
0372 
0373     default:
0374         break;
0375     }
0376 }
0377 
0378 void Display::checkX11Events()
0379 {
0380     X11::XEvent event;
0381     long eventMask = ExposureMask | PropertyChangeMask;
0382 
0383     while (X11::XCheckMaskEvent(m_display, eventMask, &event) == True) {
0384         X11::XcmeContext_InLoop(m_xcmeContext, &event);
0385         handleEvent(&event);
0386     }
0387 }
0388 
0389 
0390 } // KolorServer namespace
0391 
0392 #include "moc_display.cpp"