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