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