File indexing completed on 2024-04-28 05:35:30

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