/******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "main.h" #include //#define QT_CLEAN_NAMESPACE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config-workspace.h" #ifdef HAVE_UNISTD_H #include #endif // HAVE_UNISTD_H #ifdef HAVE_MALLOC_H #include #endif // HAVE_MALLOC_H #include #include "atoms.h" #include "options.h" #include "sm.h" #include "utils.h" #include "effects.h" #include "workspace.h" #include "xcbutils.h" #define INT8 _X11INT8 #define INT32 _X11INT32 #include #undef INT8 #undef INT32 namespace KWin { Options* options; Atoms* atoms; int screen_number = -1; bool is_multihead = false; bool initting = false; /** * Whether to run Xlib in synchronous mode and print backtraces for X errors. * Note that you most probably need to configure cmake with "-D__KDE_HAVE_GCC_VISIBILITY=0" * and -rdynamic in CXXFLAGS for kBacktrace() to work. */ static bool kwin_sync = false; //************************************ // KWinSelectionOwner //************************************ KWinSelectionOwner::KWinSelectionOwner(int screen_P) : KSelectionOwner(make_selection_atom(screen_P), screen_P) { } Atom KWinSelectionOwner::make_selection_atom(int screen_P) { if (screen_P < 0) screen_P = DefaultScreen(display()); char tmp[ 30 ]; sprintf(tmp, "WM_S%d", screen_P); return XInternAtom(display(), tmp, False); } void KWinSelectionOwner::getAtoms() { KSelectionOwner::getAtoms(); if (xa_version == None) { Atom atoms[ 1 ]; const char* const names[] = { "VERSION" }; XInternAtoms(display(), const_cast< char** >(names), 1, False, atoms); xa_version = atoms[ 0 ]; } } void KWinSelectionOwner::replyTargets(xcb_atom_t property_P, xcb_window_t requestor_P) { KSelectionOwner::replyTargets(property_P, requestor_P); Atom atoms[ 1 ] = { xa_version }; // PropModeAppend ! XChangeProperty(display(), requestor_P, property_P, XA_ATOM, 32, PropModeAppend, reinterpret_cast< unsigned char* >(atoms), 1); } bool KWinSelectionOwner::genericReply(xcb_atom_t target_P, xcb_atom_t property_P, xcb_window_t requestor_P) { if (target_P == xa_version) { long version[] = { 2, 0 }; XChangeProperty(display(), requestor_P, property_P, XA_INTEGER, 32, PropModeReplace, reinterpret_cast< unsigned char* >(&version), 2); } else return KSelectionOwner::genericReply(target_P, property_P, requestor_P); return true; } Atom KWinSelectionOwner::xa_version = None; // errorMessage is only used ifndef NDEBUG, and only in one place. // it might be worth reevaluating why this is used? I don't know. #ifndef NDEBUG /** * Outputs: "Error: (), Request: (), Resource: " */ // This is copied from KXErrorHandler and modified to explicitly use known extensions static QByteArray errorMessage(const XErrorEvent& event, Display* dpy) { QByteArray ret; char tmp[256]; char num[256]; if (event.request_code < 128) { // Core request XGetErrorText(dpy, event.error_code, tmp, 255); // The explanation in parentheses just makes // it more verbose and is not really useful if (char* paren = strchr(tmp, '(')) * paren = '\0'; // The various casts are to get overloads non-ambiguous :-/ ret = QByteArray("error: ") + (const char*)(tmp) + '[' + QByteArray::number(event.error_code) + ']'; sprintf(num, "%d", event.request_code); XGetErrorDatabaseText(dpy, "XRequest", num, "", tmp, 256); ret += QByteArray(", request: ") + (const char*)(tmp) + '[' + QByteArray::number(event.request_code) + ']'; if (event.resourceid != 0) ret += QByteArray(", resource: 0x") + QByteArray::number(qlonglong(event.resourceid), 16); } else { // Extensions // XGetErrorText() currently has a bug that makes it fail to find text // for some errors (when error==error_base), also XGetErrorDatabaseText() // requires the right extension name, so it is needed to get info about // all extensions. However that is almost impossible: // - Xlib itself has it, but in internal data. // - Opening another X connection now can cause deadlock with server grabs. // - Fetching it at startup means a bunch of roundtrips. // KWin here explicitly uses known extensions. XGetErrorText(dpy, event.error_code, tmp, 255); int index = -1; int base = 0; QVector extensions = Xcb::Extensions::self()->extensions(); for (int i = 0; i < extensions.size(); ++i) { const Xcb::ExtensionData &extension = extensions.at(i); if (extension.errorBase != 0 && event.error_code >= extension.errorBase && (index == -1 || extension.errorBase > base)) { index = i; base = extension.errorBase; } } if (tmp == QString::number(event.error_code)) { // XGetErrorText() failed or it has a bug that causes not finding all errors, check ourselves if (index != -1) { snprintf(num, 255, "%s.%d", extensions.at(index).name.constData(), event.error_code - base); XGetErrorDatabaseText(dpy, "XProtoError", num, "", tmp, 255); } else strcpy(tmp, ""); } if (char* paren = strchr(tmp, '(')) * paren = '\0'; if (index != -1) ret = QByteArray("error: ") + (const char*)(tmp) + '[' + extensions.at(index).name + '+' + QByteArray::number(event.error_code - base) + ']'; else ret = QByteArray("error: ") + (const char*)(tmp) + '[' + QByteArray::number(event.error_code) + ']'; tmp[0] = '\0'; for (int i = 0; i < extensions.size(); ++i) if (extensions.at(i).majorOpcode == event.request_code) { snprintf(num, 255, "%s.%d", extensions.at(i).name.constData(), event.minor_code); XGetErrorDatabaseText(dpy, "XRequest", num, "", tmp, 255); ret += QByteArray(", request: ") + (const char*)(tmp) + '[' + extensions.at(i).name + '+' + QByteArray::number(event.minor_code) + ']'; } if (tmp[0] == '\0') // Not found? ret += QByteArray(", request [") + QByteArray::number(event.request_code) + ':' + QByteArray::number(event.minor_code) + ']'; if (event.resourceid != 0) ret += QByteArray(", resource: 0x") + QByteArray::number(qlonglong(event.resourceid), 16); } return ret; } #endif static int x11ErrorHandler(Display* d, XErrorEvent* e) { #if KWIN_QT5_PORTING Q_UNUSED(d); bool ignore_badwindow = true; // Might be temporary if (initting && (e->request_code == X_ChangeWindowAttributes || e->request_code == X_GrabKey) && e->error_code == BadAccess) { fputs(i18n("kwin: it looks like there's already a window manager running. kwin not started.\n").toLocal8Bit(), stderr); exit(1); } if (ignore_badwindow && (e->error_code == BadWindow || e->error_code == BadColor)) return 0; #ifndef NDEBUG //fprintf( stderr, "kwin: X Error (%s)\n", KXErrorHandler::errorMessage( *e, d ).data()); kWarning(1212) << "kwin: X Error (" << errorMessage(*e, d) << ")"; #endif if (kwin_sync) fprintf(stderr, "%s\n", kBacktrace().toLocal8Bit().data()); #endif return 0; } class AlternativeWMDialog : public KDialog { public: AlternativeWMDialog() : KDialog() { setButtons(KDialog::Ok | KDialog::Cancel); QWidget* mainWidget = new QWidget(this); QVBoxLayout* layout = new QVBoxLayout(mainWidget); QString text = i18n( "KWin is unstable.\n" "It seems to have crashed several times in a row.\n" "You can select another window manager to run:"); QLabel* textLabel = new QLabel(text, mainWidget); layout->addWidget(textLabel); wmList = new KComboBox(mainWidget); wmList->setEditable(true); layout->addWidget(wmList); addWM(QStringLiteral("metacity")); addWM(QStringLiteral("openbox")); addWM(QStringLiteral("fvwm2")); addWM(QStringLiteral(KWIN_NAME)); setMainWidget(mainWidget); raise(); centerOnScreen(this); } void addWM(const QString& wm) { // TODO: Check if WM is installed if (!KStandardDirs::findExe(wm).isEmpty()) wmList->addItem(wm); } QString selectedWM() const { return wmList->currentText(); } private: KComboBox* wmList; }; int Application::crashes = 0; Application::Application() : KApplication() , owner(screen_number) { if (KCmdLineArgs::parsedArgs("qt")->isSet("sync")) { kwin_sync = true; XSynchronize(display(), True); kDebug(1212) << "Running KWin in sync mode"; } setQuitOnLastWindowClosed(false); KCmdLineArgs* args = KCmdLineArgs::parsedArgs(); KSharedConfig::Ptr config = KGlobal::config(); if (!config->isImmutable() && args->isSet("lock")) { // TODO: This shouldn't be necessary //config->setReadOnly( true ); config->reparseConfiguration(); } if (screen_number == -1) screen_number = DefaultScreen(display()); connect(&owner, &KSelectionOwner::failedToClaimOwnership, []{ fputs(i18n("kwin: unable to claim manager selection, another wm running? (try using --replace)\n").toLocal8Bit().constData(), stderr); ::exit(1); }); connect(&owner, SIGNAL(lostOwnership()), SLOT(lostSelection())); owner.claim(args->isSet("replace"), true); KCrash::setEmergencySaveFunction(Application::crashHandler); crashes = args->getOption("crashes").toInt(); if (crashes >= 4) { // Something has gone seriously wrong AlternativeWMDialog dialog; QString cmd = QStringLiteral(KWIN_NAME); if (dialog.exec() == QDialog::Accepted) cmd = dialog.selectedWM(); else ::exit(1); if (cmd.length() > 500) { kDebug(1212) << "Command is too long, truncating"; cmd = cmd.left(500); } kDebug(1212) << "Starting" << cmd << "and exiting"; char buf[1024]; sprintf(buf, "%s &", cmd.toAscii().data()); system(buf); ::exit(1); } if (crashes >= 2) { // Disable compositing if we have had too many crashes kDebug(1212) << "Too many crashes recently, disabling compositing"; KConfigGroup compgroup(config, "Compositing"); compgroup.writeEntry("Enabled", false); } // Reset crashes count if we stay up for more that 15 seconds QTimer::singleShot(15 * 1000, this, SLOT(resetCrashesCount())); initting = true; // Startup... // first load options - done internally by a different thread options = new Options; // Install X11 error handler XSetErrorHandler(x11ErrorHandler); // Check whether another windowmanager is running XSelectInput(display(), rootWindow(), SubstructureRedirectMask); syncX(); // Trigger error now atoms = new Atoms; // initting = false; // TODO // This tries to detect compositing options and can use GLX. GLX problems // (X errors) shouldn't cause kwin to abort, so this is out of the // critical startup section where x errors cause kwin to abort. // create workspace. (void) new Workspace(isSessionRestored()); syncX(); // Trigger possible errors, there's still a chance to abort initting = false; // Startup done, we are up and running now. XEvent e; e.xclient.type = ClientMessage; e.xclient.message_type = XInternAtom(display(), "_KDE_SPLASH_PROGRESS", False); e.xclient.display = display(); e.xclient.window = rootWindow(); e.xclient.format = 8; strcpy(e.xclient.data.b, "wm"); XSendEvent(display(), rootWindow(), False, SubstructureNotifyMask, &e); } Application::~Application() { delete Workspace::self(); if (owner.ownerWindow() != None) // If there was no --replace (no new WM) XSetInputFocus(display(), PointerRoot, RevertToPointerRoot, xTime()); delete options; delete effects; delete atoms; } void Application::lostSelection() { sendPostedEvents(); delete Workspace::self(); // Remove windowmanager privileges XSelectInput(display(), rootWindow(), PropertyChangeMask); quit(); } bool Application::notify(QObject* o, QEvent* e) { if (Workspace::self()->workspaceEvent(e)) return true; return KApplication::notify(o, e); } static void sighandler(int) { QApplication::exit(); } void Application::crashHandler(int signal) { crashes++; fprintf(stderr, "Application::crashHandler() called with signal %d; recent crashes: %d\n", signal, crashes); char cmd[1024]; sprintf(cmd, "%s --crashes %d &", QFile::encodeName(QCoreApplication::applicationFilePath()).constData(), crashes); sleep(1); system(cmd); } void Application::resetCrashesCount() { crashes = 0; } } // namespace static const char version[] = KDE_VERSION_STRING; static const char description[] = I18N_NOOP("KDE window manager"); extern "C" KDE_EXPORT int kdemain(int argc, char * argv[]) { #ifdef M_TRIM_THRESHOLD // Prevent fragmentation of the heap by malloc (glibc). // // The default threshold is 128*1024, which can result in a large memory usage // due to fragmentation especially if we use the raster graphicssystem. On the // otherside if the threshold is too low, free() starts to permanently ask the kernel // about shrinking the heap. #ifdef HAVE_UNISTD_H const int pagesize = sysconf(_SC_PAGESIZE); #else const int pagesize = 4*1024; #endif // HAVE_UNISTD_H mallopt(M_TRIM_THRESHOLD, 5*pagesize); #endif // M_TRIM_THRESHOLD Display* dpy = XOpenDisplay(NULL); if (!dpy) { fprintf(stderr, "%s: FATAL ERROR while trying to open display %s\n", argv[0], XDisplayName(NULL)); exit(1); } int number_of_screens = ScreenCount(dpy); // multi head if (number_of_screens != 1 && KGlobalSettings::isMultiHead()) { KWin::is_multihead = true; KWin::screen_number = DefaultScreen(dpy); int pos; // Temporarily needed to reconstruct DISPLAY var if multi-head QByteArray display_name = XDisplayString(dpy); XCloseDisplay(dpy); dpy = 0; if ((pos = display_name.lastIndexOf('.')) != -1) display_name.remove(pos, 10); // 10 is enough to be sure we removed ".s" QString envir; for (int i = 0; i < number_of_screens; i++) { // If execution doesn't pass by here, then kwin // acts exactly as previously if (i != KWin::screen_number && fork() == 0) { KWin::screen_number = i; // Break here because we are the child process, we don't // want to fork() anymore break; } } // In the next statement, display_name shouldn't contain a screen // number. If it had it, it was removed at the "pos" check envir.sprintf("DISPLAY=%s.%d", display_name.data(), KWin::screen_number); if (putenv(strdup(envir.toAscii().constData()))) { fprintf(stderr, "%s: WARNING: unable to set DISPLAY environment variable\n", argv[0]); perror("putenv()"); } } K4AboutData aboutData( QByteArray(KWIN_NAME), // The program name used internally QByteArray(), // The message catalog name. If null, program name is used instead ki18n("KWin"), // A displayable program name string QByteArray(version), // The program version string ki18n(description), // Short description of what the app does K4AboutData::License_GPL, // The license this code is released under ki18n("(c) 1999-2008, The KDE Developers")); // Copyright Statement aboutData.addAuthor(ki18n("Matthias Ettrich"), KLocalizedString(), "ettrich@kde.org"); aboutData.addAuthor(ki18n("Cristian Tibirna"), KLocalizedString(), "tibirna@kde.org"); aboutData.addAuthor(ki18n("Daniel M. Duley"), KLocalizedString(), "mosfet@kde.org"); aboutData.addAuthor(ki18n("Luboš Luňák"), KLocalizedString(), "l.lunak@kde.org"); aboutData.addAuthor(ki18n("Martin Gräßlin"), ki18n("Maintainer"), "mgraesslin@kde.org"); KCmdLineArgs::init(argc, argv, &aboutData); KCmdLineOptions args; args.add("lock", ki18n("Disable configuration options")); args.add("replace", ki18n("Replace already-running ICCCM2.0-compliant window manager")); args.add("crashes ", ki18n("Indicate that KWin has recently crashed n times")); KCmdLineArgs::addCmdLineOptions(args); if (KDE_signal(SIGTERM, KWin::sighandler) == SIG_IGN) KDE_signal(SIGTERM, SIG_IGN); if (KDE_signal(SIGINT, KWin::sighandler) == SIG_IGN) KDE_signal(SIGINT, SIG_IGN); if (KDE_signal(SIGHUP, KWin::sighandler) == SIG_IGN) KDE_signal(SIGHUP, SIG_IGN); // Disable the glib event loop integration, since it seems to be responsible // for several bug reports about high CPU usage (bug #239963) setenv("QT_NO_GLIB", "1", true); org::kde::KSMServerInterface ksmserver(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"), QDBusConnection::sessionBus()); ksmserver.suspendStartup(QStringLiteral(KWIN_NAME)); KWin::Application a; ksmserver.resumeStartup(QStringLiteral(KWIN_NAME)); KWin::SessionManager weAreIndeed; KWin::SessionSaveDoneHelper helper; #warning insertCatalog needs porting #if KWIN_QT5_PORTING KGlobal::locale()->insertCatalog("kwin_effects"); KGlobal::locale()->insertCatalog("kwin_scripts"); KGlobal::locale()->insertCatalog("kwin_scripting"); #endif // Announce when KWIN_DIRECT_GL is set for above HACK if (qstrcmp(qgetenv("KWIN_DIRECT_GL"), "1") == 0) kDebug(1212) << "KWIN_DIRECT_GL set, not forcing LIBGL_ALWAYS_INDIRECT=1"; fcntl(XConnectionNumber(KWin::display()), F_SETFD, 1); QString appname; if (KWin::screen_number == 0) appname = QStringLiteral("org.kde.kwin"); else appname.sprintf("org.kde.kwin-screen-%d", KWin::screen_number); QDBusConnection::sessionBus().interface()->registerService( appname, QDBusConnectionInterface::DontQueueService); KCmdLineArgs* sargs = KCmdLineArgs::parsedArgs(); return a.exec(); } #include "main.moc"