/* ksmserver - the KDE session management server SPDX-FileCopyrightText: 2000 Matthias Ettrich SPDX-FileCopyrightText: 2005 Lubos Lunak SPDX-FileContributor: Oswald Buddenhagen some code taken from the dcopserver (part of the KDE libraries), which is SPDX-FileCopyrightText: 1999 Matthias Ettrich SPDX-FileCopyrightText: 1999 Preston Brown SPDX-License-Identifier: MIT */ #include #include #include #include #include #ifdef HAVE_SYS_TIME_H #include #endif #include "server.h" #include #include #include #include #include #include #include #include #include #include #include /* * Legacy session management */ #ifndef NO_LEGACY_SESSION_MANAGEMENT static WindowMap *windowMapPtr = nullptr; static Atom wm_save_yourself = XNone; static Atom wm_protocols = XNone; static Atom wm_client_leader = XNone; static Atom sm_client_id = XNone; static int winsErrorHandler(Display *, XErrorEvent *ev) { if (windowMapPtr) { WindowMap::Iterator it = windowMapPtr->find(ev->resourceid); if (it != windowMapPtr->end()) (*it).type = SM_ERROR; } return 0; } void KSMServer::performLegacySessionSave() { qCDebug(KSMSERVER) << "Saving legacy session apps"; if (state == ClosingSubSession) return; // FIXME implement later KSharedConfig::Ptr config = KSharedConfig::openConfig(); config->reparseConfiguration(); // config may have changed in the KControl module KConfigGroup cg(config, QStringLiteral("General")); int wmSaveYourselfTimeout = cg.readEntry("legacySaveTimeoutSecs", 4) * 1000; // Setup error handler legacyWindows.clear(); windowMapPtr = &legacyWindows; XErrorHandler oldHandler = XSetErrorHandler(winsErrorHandler); // Compute set of leader windows that need legacy session management // and determine which style (WM_COMMAND or WM_SAVE_YOURSELF) auto display = qGuiApp->nativeInterface()->display(); if (wm_save_yourself == (Atom)XNone) { Atom atoms[4]; const char *const names[] = {"WM_SAVE_YOURSELF", "WM_PROTOCOLS", "WM_CLIENT_LEADER", "SM_CLIENT_ID"}; XInternAtoms(display, const_cast(names), 4, False, atoms); wm_save_yourself = atoms[0]; wm_protocols = atoms[1]; wm_client_leader = atoms[2]; sm_client_id = atoms[3]; } const QList windows = KX11Extras::windows(); for (QList::ConstIterator it = windows.begin(); it != windows.end(); ++it) { WId leader = windowWmClientLeader(*it); if (!legacyWindows.contains(leader) && windowSessionId(*it, leader).isEmpty()) { SMType wtype = SM_WMCOMMAND; int nprotocols = 0; Atom *protocols = nullptr; if (XGetWMProtocols(display, leader, &protocols, &nprotocols)) { for (int i = 0; i < nprotocols; i++) if (protocols[i] == wm_save_yourself) { wtype = SM_WMSAVEYOURSELF; break; } XFree((void *)protocols); } SMData data; data.type = wtype; XClassHint classHint; if (XGetClassHint(display, leader, &classHint)) { data.wmclass1 = QString::fromLocal8Bit(classHint.res_name); data.wmclass2 = QString::fromLocal8Bit(classHint.res_class); XFree(classHint.res_name); XFree(classHint.res_class); } legacyWindows.insert(leader, data); } } // Open fresh display for sending WM_SAVE_YOURSELF XSync(display, False); Display *newdisplay = XOpenDisplay(DisplayString(display)); if (!newdisplay) { windowMapPtr = nullptr; XSetErrorHandler(oldHandler); return; } WId root = DefaultRootWindow(newdisplay); XGrabKeyboard(newdisplay, root, False, GrabModeAsync, GrabModeAsync, CurrentTime); XGrabPointer(newdisplay, root, False, Button1Mask | Button2Mask | Button3Mask, GrabModeAsync, GrabModeAsync, XNone, XNone, CurrentTime); // Send WM_SAVE_YOURSELF messages XEvent ev; int awaiting_replies = 0; for (WindowMap::Iterator it = legacyWindows.begin(); it != legacyWindows.end(); ++it) { if ((*it).type == SM_WMSAVEYOURSELF) { WId w = it.key(); awaiting_replies += 1; memset(&ev, 0, sizeof(ev)); ev.xclient.type = ClientMessage; ev.xclient.window = w; ev.xclient.message_type = wm_protocols; ev.xclient.format = 32; ev.xclient.data.l[0] = wm_save_yourself; ev.xclient.data.l[1] = XCB_CURRENT_TIME; XSelectInput(newdisplay, w, PropertyChangeMask | StructureNotifyMask); XSendEvent(newdisplay, w, False, 0, &ev); qCDebug(KSMSERVER) << "sent >save yourself< to legacy app " << (*it).wmclass1 << (*it).wmclass2; } } // Wait for change in WM_COMMAND with timeout XFlush(newdisplay); QElapsedTimer start; while (awaiting_replies > 0) { if (XPending(newdisplay)) { /* Process pending event */ XNextEvent(newdisplay, &ev); if ((ev.xany.type == UnmapNotify) || (ev.xany.type == PropertyNotify && ev.xproperty.atom == XA_WM_COMMAND)) { WindowMap::Iterator it = legacyWindows.find(ev.xany.window); if (it != legacyWindows.end() && (*it).type != SM_WMCOMMAND) { awaiting_replies -= 1; if ((*it).type != SM_ERROR) (*it).type = SM_WMCOMMAND; } } } else { /* Check timeout */ int msecs = start.elapsed(); if (msecs >= wmSaveYourselfTimeout) { qCDebug(KSMSERVER) << "legacy timeout expired"; break; } /* Wait for more events */ fd_set fds; FD_ZERO(&fds); int fd = ConnectionNumber(newdisplay); FD_SET(fd, &fds); struct timeval tmwait; tmwait.tv_sec = (wmSaveYourselfTimeout - msecs) / 1000; tmwait.tv_usec = ((wmSaveYourselfTimeout - msecs) % 1000) * 1000; ::select(fd + 1, &fds, nullptr, &fds, &tmwait); } } // Terminate work in new display XAllowEvents(newdisplay, ReplayPointer, CurrentTime); XAllowEvents(newdisplay, ReplayKeyboard, CurrentTime); XSync(newdisplay, False); XCloseDisplay(newdisplay); // Restore old error handler XSync(display, False); XSetErrorHandler(oldHandler); for (WindowMap::Iterator it = legacyWindows.begin(); it != legacyWindows.end(); ++it) { if ((*it).type != SM_ERROR) { WId w = it.key(); (*it).wmCommand = windowWmCommand(w); (*it).wmClientMachine = windowWmClientMachine(w); } } qCDebug(KSMSERVER) << "Done saving " << legacyWindows.count() << " legacy session apps"; } /*! Stores legacy session management data */ void KSMServer::storeLegacySession(KConfig *config) { if (state == ClosingSubSession) return; // FIXME implement later // Write LegacySession data config->deleteGroup(QString(u"Legacy" + sessionGroup)); KConfigGroup group(config, QString(u"Legacy" + sessionGroup)); int count = 0; for (WindowMap::ConstIterator it = legacyWindows.constBegin(); it != legacyWindows.constEnd(); ++it) { if ((*it).type != SM_ERROR) { if (excludeApps.contains((*it).wmclass1.toLower()) || excludeApps.contains((*it).wmclass2.toLower())) continue; if (!(*it).wmCommand.isEmpty() && !(*it).wmClientMachine.isEmpty()) { count++; const QString n = QString::number(count); group.writeEntry(QString(u"command" + n), (*it).wmCommand); group.writeEntry(QString(u"clientMachine" + n), (*it).wmClientMachine); } } } group.writeEntry("count", count); } /*! Restores legacy session management data (i.e. restart applications) */ void KSMServer::restoreLegacySession(KConfig *config) { if (config->hasGroup(QString(u"Legacy" + sessionGroup))) { KConfigGroup group(config, QString(u"Legacy" + sessionGroup)); restoreLegacySessionInternal(&group); } } void KSMServer::restoreLegacySessionInternal(KConfigGroup *config, char sep) { int count = config->readEntry("count", 0); for (int i = 1; i <= count; i++) { QString n = QString::number(i); QStringList wmCommand = (sep == ',') ? // why is this named "wmCommand"? config->readEntry(QString(u"command" + n), QStringList()) : KShell::splitArgs(config->readEntry(QString(u"command" + n), QString())); // close enough(?) if (wmCommand.isEmpty()) continue; startApplication(wmCommand, config->readEntry(QString(u"clientMachine" + n), QString()), config->readEntry(QString(u"userId" + n), QString())); } } static QByteArray getQCStringProperty(WId w, Atom prop) { Atom type; int format, status; unsigned long nitems = 0; unsigned long extra = 0; unsigned char *data = nullptr; auto display = qGuiApp->nativeInterface()->display(); QByteArray result = ""; status = XGetWindowProperty(display, w, prop, 0, 10000, false, XA_STRING, &type, &format, &nitems, &extra, &data); if (status == Success) { if (data) result = (char *)data; XFree(data); } return result; } static QStringList getQStringListProperty(WId w, Atom prop) { Atom type; int format, status; unsigned long nitems = 0; unsigned long extra = 0; unsigned char *data = nullptr; auto display = qGuiApp->nativeInterface()->display(); QStringList result; status = XGetWindowProperty(display, w, prop, 0, 10000, false, XA_STRING, &type, &format, &nitems, &extra, &data); if (status == Success) { if (!data) return result; for (int i = 0; i < (int)nitems; i++) { result << QLatin1String((const char *)data + i); while (data[i]) i++; } XFree(data); } return result; } QStringList KSMServer::windowWmCommand(WId w) { QStringList ret = getQStringListProperty(w, XA_WM_COMMAND); // hacks here if (ret.count() == 1) { QString command = ret.first(); // Mozilla is launched using wrapper scripts, so it's launched using "mozilla", // but the actual binary is "mozilla-bin" or "/mozilla-bin", and that's what // will be also in WM_COMMAND - using this "mozilla-bin" doesn't work at all though if (command.endsWith(QLatin1String("mozilla-bin"))) return QStringList() << QStringLiteral("mozilla"); if (command.endsWith(QLatin1String("firefox-bin"))) return QStringList() << QStringLiteral("firefox"); if (command.endsWith(QLatin1String("thunderbird-bin"))) return QStringList() << QStringLiteral("thunderbird"); if (command.endsWith(QLatin1String("sunbird-bin"))) return QStringList() << QStringLiteral("sunbird"); if (command.endsWith(QLatin1String("seamonkey-bin"))) return QStringList() << QStringLiteral("seamonkey"); } return ret; } QString KSMServer::windowWmClientMachine(WId w) { QByteArray result = getQCStringProperty(w, XA_WM_CLIENT_MACHINE); if (result.isEmpty()) { result = "localhost"; } else { // special name for the local machine (localhost) char hostnamebuf[80]; if (gethostname(hostnamebuf, sizeof hostnamebuf) >= 0) { hostnamebuf[sizeof(hostnamebuf) - 1] = 0; if (result == hostnamebuf) result = "localhost"; if (char *dot = strchr(hostnamebuf, '.')) { *dot = '\0'; if (result == hostnamebuf) result = "localhost"; } } } return QLatin1String(result); } WId KSMServer::windowWmClientLeader(WId w) { Atom type; int format, status; unsigned long nitems = 0; unsigned long extra = 0; unsigned char *data = nullptr; auto display = qGuiApp->nativeInterface()->display(); Window result = w; status = XGetWindowProperty(display, w, wm_client_leader, 0, 10000, false, XA_WINDOW, &type, &format, &nitems, &extra, &data); if (status == Success) { if (data && nitems > 0) result = *((Window *)data); XFree(data); } return result; } /* Returns sessionId for this client, taken either from its window or from the leader window. */ QByteArray KSMServer::windowSessionId(WId w, WId leader) { QByteArray result = getQCStringProperty(w, sm_client_id); if (result.isEmpty() && leader != (WId)None && leader != w) result = getQCStringProperty(leader, sm_client_id); return result; } #endif