Warning, file /plasma/plasma-workspace/ksmserver/legacy.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     ksmserver - the KDE session management server
0003 
0004     SPDX-FileCopyrightText: 2000 Matthias Ettrich <ettrich@kde.org>
0005     SPDX-FileCopyrightText: 2005 Lubos Lunak <l.lunak@kde.org>
0006 
0007     SPDX-FileContributor: Oswald Buddenhagen <ob6@inf.tu-dresden.de>
0008 
0009     some code taken from the dcopserver (part of the KDE libraries), which is
0010     SPDX-FileCopyrightText: 1999 Matthias Ettrich <ettrich@kde.org>
0011     SPDX-FileCopyrightText: 1999 Preston Brown <pbrown@kde.org>
0012 
0013     SPDX-License-Identifier: MIT
0014 */
0015 
0016 #include <QDebug>
0017 #include <QElapsedTimer>
0018 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0019 #include <private/qtx11extras_p.h>
0020 #else
0021 #include <QX11Info>
0022 #endif
0023 
0024 #include <config-workspace.h>
0025 
0026 #include <ksmserver_debug.h>
0027 
0028 #ifdef HAVE_SYS_TIME_H
0029 #include <sys/time.h>
0030 #endif
0031 
0032 #include "server.h"
0033 
0034 #include <unistd.h>
0035 
0036 #include <KSharedConfig>
0037 #include <KX11Extras>
0038 #include <kconfig.h>
0039 #include <kconfiggroup.h>
0040 #include <kshell.h>
0041 #include <kwindowsystem.h>
0042 
0043 #include <X11/Xatom.h>
0044 #include <X11/Xlib.h>
0045 #include <X11/Xutil.h>
0046 
0047 /*
0048  * Legacy session management
0049  */
0050 
0051 #ifndef NO_LEGACY_SESSION_MANAGEMENT
0052 static WindowMap *windowMapPtr = nullptr;
0053 
0054 static Atom wm_save_yourself = XNone;
0055 static Atom wm_protocols = XNone;
0056 static Atom wm_client_leader = XNone;
0057 static Atom sm_client_id = XNone;
0058 
0059 static int winsErrorHandler(Display *, XErrorEvent *ev)
0060 {
0061     if (windowMapPtr) {
0062         WindowMap::Iterator it = windowMapPtr->find(ev->resourceid);
0063         if (it != windowMapPtr->end())
0064             (*it).type = SM_ERROR;
0065     }
0066     return 0;
0067 }
0068 
0069 void KSMServer::performLegacySessionSave()
0070 {
0071     qCDebug(KSMSERVER) << "Saving legacy session apps";
0072     if (state == ClosingSubSession)
0073         return; // FIXME implement later
0074 
0075     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0076     config->reparseConfiguration(); // config may have changed in the KControl module
0077     KConfigGroup cg(config, "General");
0078 
0079     int wmSaveYourselfTimeout = cg.readEntry("legacySaveTimeoutSecs", 4) * 1000;
0080 
0081     // Setup error handler
0082     legacyWindows.clear();
0083     windowMapPtr = &legacyWindows;
0084     XErrorHandler oldHandler = XSetErrorHandler(winsErrorHandler);
0085     // Compute set of leader windows that need legacy session management
0086     // and determine which style (WM_COMMAND or WM_SAVE_YOURSELF)
0087     if (wm_save_yourself == (Atom)XNone) {
0088         Atom atoms[4];
0089         const char *const names[] = {"WM_SAVE_YOURSELF", "WM_PROTOCOLS", "WM_CLIENT_LEADER", "SM_CLIENT_ID"};
0090         XInternAtoms(QX11Info::display(), const_cast<char **>(names), 4, False, atoms);
0091         wm_save_yourself = atoms[0];
0092         wm_protocols = atoms[1];
0093         wm_client_leader = atoms[2];
0094         sm_client_id = atoms[3];
0095     }
0096     const QList<WId> windows = KX11Extras::windows();
0097     for (QList<WId>::ConstIterator it = windows.begin(); it != windows.end(); ++it) {
0098         WId leader = windowWmClientLeader(*it);
0099         if (!legacyWindows.contains(leader) && windowSessionId(*it, leader).isEmpty()) {
0100             SMType wtype = SM_WMCOMMAND;
0101             int nprotocols = 0;
0102             Atom *protocols = nullptr;
0103             if (XGetWMProtocols(QX11Info::display(), leader, &protocols, &nprotocols)) {
0104                 for (int i = 0; i < nprotocols; i++)
0105                     if (protocols[i] == wm_save_yourself) {
0106                         wtype = SM_WMSAVEYOURSELF;
0107                         break;
0108                     }
0109                 XFree((void *)protocols);
0110             }
0111             SMData data;
0112             data.type = wtype;
0113             XClassHint classHint;
0114             if (XGetClassHint(QX11Info::display(), leader, &classHint)) {
0115                 data.wmclass1 = QString::fromLocal8Bit(classHint.res_name);
0116                 data.wmclass2 = QString::fromLocal8Bit(classHint.res_class);
0117                 XFree(classHint.res_name);
0118                 XFree(classHint.res_class);
0119             }
0120             legacyWindows.insert(leader, data);
0121         }
0122     }
0123     // Open fresh display for sending WM_SAVE_YOURSELF
0124     XSync(QX11Info::display(), False);
0125     Display *newdisplay = XOpenDisplay(DisplayString(QX11Info::display()));
0126     if (!newdisplay) {
0127         windowMapPtr = nullptr;
0128         XSetErrorHandler(oldHandler);
0129         return;
0130     }
0131     WId root = DefaultRootWindow(newdisplay);
0132     XGrabKeyboard(newdisplay, root, False, GrabModeAsync, GrabModeAsync, CurrentTime);
0133     XGrabPointer(newdisplay, root, False, Button1Mask | Button2Mask | Button3Mask, GrabModeAsync, GrabModeAsync, XNone, XNone, CurrentTime);
0134     // Send WM_SAVE_YOURSELF messages
0135     XEvent ev;
0136     int awaiting_replies = 0;
0137     for (WindowMap::Iterator it = legacyWindows.begin(); it != legacyWindows.end(); ++it) {
0138         if ((*it).type == SM_WMSAVEYOURSELF) {
0139             WId w = it.key();
0140             awaiting_replies += 1;
0141             memset(&ev, 0, sizeof(ev));
0142             ev.xclient.type = ClientMessage;
0143             ev.xclient.window = w;
0144             ev.xclient.message_type = wm_protocols;
0145             ev.xclient.format = 32;
0146             ev.xclient.data.l[0] = wm_save_yourself;
0147             ev.xclient.data.l[1] = QX11Info::appTime();
0148             XSelectInput(newdisplay, w, PropertyChangeMask | StructureNotifyMask);
0149             XSendEvent(newdisplay, w, False, 0, &ev);
0150             qCDebug(KSMSERVER) << "sent >save yourself< to legacy app " << (*it).wmclass1 << (*it).wmclass2;
0151         }
0152     }
0153     // Wait for change in WM_COMMAND with timeout
0154     XFlush(newdisplay);
0155     QElapsedTimer start;
0156     while (awaiting_replies > 0) {
0157         if (XPending(newdisplay)) {
0158             /* Process pending event */
0159             XNextEvent(newdisplay, &ev);
0160             if ((ev.xany.type == UnmapNotify) || (ev.xany.type == PropertyNotify && ev.xproperty.atom == XA_WM_COMMAND)) {
0161                 WindowMap::Iterator it = legacyWindows.find(ev.xany.window);
0162                 if (it != legacyWindows.end() && (*it).type != SM_WMCOMMAND) {
0163                     awaiting_replies -= 1;
0164                     if ((*it).type != SM_ERROR)
0165                         (*it).type = SM_WMCOMMAND;
0166                 }
0167             }
0168         } else {
0169             /* Check timeout */
0170             int msecs = start.elapsed();
0171             if (msecs >= wmSaveYourselfTimeout) {
0172                 qCDebug(KSMSERVER) << "legacy timeout expired";
0173                 break;
0174             }
0175             /* Wait for more events */
0176             fd_set fds;
0177             FD_ZERO(&fds);
0178             int fd = ConnectionNumber(newdisplay);
0179             FD_SET(fd, &fds);
0180             struct timeval tmwait;
0181             tmwait.tv_sec = (wmSaveYourselfTimeout - msecs) / 1000;
0182             tmwait.tv_usec = ((wmSaveYourselfTimeout - msecs) % 1000) * 1000;
0183             ::select(fd + 1, &fds, nullptr, &fds, &tmwait);
0184         }
0185     }
0186     // Terminate work in new display
0187     XAllowEvents(newdisplay, ReplayPointer, CurrentTime);
0188     XAllowEvents(newdisplay, ReplayKeyboard, CurrentTime);
0189     XSync(newdisplay, False);
0190     XCloseDisplay(newdisplay);
0191     // Restore old error handler
0192     XSync(QX11Info::display(), False);
0193     XSetErrorHandler(oldHandler);
0194     for (WindowMap::Iterator it = legacyWindows.begin(); it != legacyWindows.end(); ++it) {
0195         if ((*it).type != SM_ERROR) {
0196             WId w = it.key();
0197             (*it).wmCommand = windowWmCommand(w);
0198             (*it).wmClientMachine = windowWmClientMachine(w);
0199         }
0200     }
0201     qCDebug(KSMSERVER) << "Done saving " << legacyWindows.count() << " legacy session apps";
0202 }
0203 
0204 /*!
0205 Stores legacy session management data
0206 */
0207 void KSMServer::storeLegacySession(KConfig *config)
0208 {
0209     if (state == ClosingSubSession)
0210         return; // FIXME implement later
0211     // Write LegacySession data
0212     config->deleteGroup(QStringLiteral("Legacy") + sessionGroup);
0213     KConfigGroup group(config, QStringLiteral("Legacy") + sessionGroup);
0214     int count = 0;
0215     for (WindowMap::ConstIterator it = legacyWindows.constBegin(); it != legacyWindows.constEnd(); ++it) {
0216         if ((*it).type != SM_ERROR) {
0217             if (excludeApps.contains((*it).wmclass1.toLower()) || excludeApps.contains((*it).wmclass2.toLower()))
0218                 continue;
0219             if (!(*it).wmCommand.isEmpty() && !(*it).wmClientMachine.isEmpty()) {
0220                 count++;
0221                 QString n = QString::number(count);
0222                 group.writeEntry(QStringLiteral("command") + n, (*it).wmCommand);
0223                 group.writeEntry(QStringLiteral("clientMachine") + n, (*it).wmClientMachine);
0224             }
0225         }
0226     }
0227     group.writeEntry("count", count);
0228 }
0229 
0230 /*!
0231 Restores legacy session management data (i.e. restart applications)
0232 */
0233 void KSMServer::restoreLegacySession(KConfig *config)
0234 {
0235     if (config->hasGroup(QStringLiteral("Legacy") + sessionGroup)) {
0236         KConfigGroup group(config, QStringLiteral("Legacy") + sessionGroup);
0237         restoreLegacySessionInternal(&group);
0238     }
0239 }
0240 
0241 void KSMServer::restoreLegacySessionInternal(KConfigGroup *config, char sep)
0242 {
0243     int count = config->readEntry("count", 0);
0244     for (int i = 1; i <= count; i++) {
0245         QString n = QString::number(i);
0246         QStringList wmCommand = (sep == ',') ? // why is this named "wmCommand"?
0247             config->readEntry(QStringLiteral("command") + n, QStringList())
0248                                              : KShell::splitArgs(config->readEntry(QStringLiteral("command") + n, QString())); // close enough(?)
0249         if (wmCommand.isEmpty())
0250             continue;
0251         startApplication(wmCommand,
0252                          config->readEntry(QStringLiteral("clientMachine") + n, QString()),
0253                          config->readEntry(QStringLiteral("userId") + n, QString()));
0254     }
0255 }
0256 
0257 static QByteArray getQCStringProperty(WId w, Atom prop)
0258 {
0259     Atom type;
0260     int format, status;
0261     unsigned long nitems = 0;
0262     unsigned long extra = 0;
0263     unsigned char *data = nullptr;
0264     QByteArray result = "";
0265     status = XGetWindowProperty(QX11Info::display(), w, prop, 0, 10000, false, XA_STRING, &type, &format, &nitems, &extra, &data);
0266     if (status == Success) {
0267         if (data)
0268             result = (char *)data;
0269         XFree(data);
0270     }
0271     return result;
0272 }
0273 
0274 static QStringList getQStringListProperty(WId w, Atom prop)
0275 {
0276     Atom type;
0277     int format, status;
0278     unsigned long nitems = 0;
0279     unsigned long extra = 0;
0280     unsigned char *data = nullptr;
0281     QStringList result;
0282 
0283     status = XGetWindowProperty(QX11Info::display(), w, prop, 0, 10000, false, XA_STRING, &type, &format, &nitems, &extra, &data);
0284     if (status == Success) {
0285         if (!data)
0286             return result;
0287         for (int i = 0; i < (int)nitems; i++) {
0288             result << QLatin1String((const char *)data + i);
0289             while (data[i])
0290                 i++;
0291         }
0292         XFree(data);
0293     }
0294     return result;
0295 }
0296 
0297 QStringList KSMServer::windowWmCommand(WId w)
0298 {
0299     QStringList ret = getQStringListProperty(w, XA_WM_COMMAND);
0300     // hacks here
0301     if (ret.count() == 1) {
0302         QString command = ret.first();
0303         // Mozilla is launched using wrapper scripts, so it's launched using "mozilla",
0304         // but the actual binary is "mozilla-bin" or "<path>/mozilla-bin", and that's what
0305         // will be also in WM_COMMAND - using this "mozilla-bin" doesn't work at all though
0306         if (command.endsWith(QLatin1String("mozilla-bin")))
0307             return QStringList() << QStringLiteral("mozilla");
0308         if (command.endsWith(QLatin1String("firefox-bin")))
0309             return QStringList() << QStringLiteral("firefox");
0310         if (command.endsWith(QLatin1String("thunderbird-bin")))
0311             return QStringList() << QStringLiteral("thunderbird");
0312         if (command.endsWith(QLatin1String("sunbird-bin")))
0313             return QStringList() << QStringLiteral("sunbird");
0314         if (command.endsWith(QLatin1String("seamonkey-bin")))
0315             return QStringList() << QStringLiteral("seamonkey");
0316     }
0317     return ret;
0318 }
0319 
0320 QString KSMServer::windowWmClientMachine(WId w)
0321 {
0322     QByteArray result = getQCStringProperty(w, XA_WM_CLIENT_MACHINE);
0323     if (result.isEmpty()) {
0324         result = "localhost";
0325     } else {
0326         // special name for the local machine (localhost)
0327         char hostnamebuf[80];
0328         if (gethostname(hostnamebuf, sizeof hostnamebuf) >= 0) {
0329             hostnamebuf[sizeof(hostnamebuf) - 1] = 0;
0330             if (result == hostnamebuf)
0331                 result = "localhost";
0332             if (char *dot = strchr(hostnamebuf, '.')) {
0333                 *dot = '\0';
0334                 if (result == hostnamebuf)
0335                     result = "localhost";
0336             }
0337         }
0338     }
0339     return QLatin1String(result);
0340 }
0341 
0342 WId KSMServer::windowWmClientLeader(WId w)
0343 {
0344     Atom type;
0345     int format, status;
0346     unsigned long nitems = 0;
0347     unsigned long extra = 0;
0348     unsigned char *data = nullptr;
0349     Window result = w;
0350     status = XGetWindowProperty(QX11Info::display(), w, wm_client_leader, 0, 10000, false, XA_WINDOW, &type, &format, &nitems, &extra, &data);
0351     if (status == Success) {
0352         if (data && nitems > 0)
0353             result = *((Window *)data);
0354         XFree(data);
0355     }
0356     return result;
0357 }
0358 
0359 /*
0360 Returns sessionId for this client,
0361 taken either from its window or from the leader window.
0362 */
0363 QByteArray KSMServer::windowSessionId(WId w, WId leader)
0364 {
0365     QByteArray result = getQCStringProperty(w, sm_client_id);
0366     if (result.isEmpty() && leader != (WId)None && leader != w)
0367         result = getQCStringProperty(leader, sm_client_id);
0368     return result;
0369 }
0370 #endif