diff --git a/dataengines/filebrowser/CMakeLists.txt b/dataengines/filebrowser/CMakeLists.txt index 70d651f59..4ee4779ac 100644 --- a/dataengines/filebrowser/CMakeLists.txt +++ b/dataengines/filebrowser/CMakeLists.txt @@ -1,17 +1,16 @@ set(filebrowser_engine_SRCS filebrowserengine.cpp ) add_library(plasma_engine_filebrowser MODULE ${filebrowser_engine_SRCS}) target_link_libraries(plasma_engine_filebrowser KF5::Plasma KF5::Service KF5::KIOCore - KF5::KDELibs4Support ) kcoreaddons_desktop_to_json(plasma_engine_filebrowser plasma-dataengine-filebrowser.desktop) install(TARGETS plasma_engine_filebrowser DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasma/dataengine) install(FILES plasma-dataengine-filebrowser.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) diff --git a/dataengines/filebrowser/filebrowserengine.cpp b/dataengines/filebrowser/filebrowserengine.cpp index d2d861eba..f5af30f6a 100644 --- a/dataengines/filebrowser/filebrowserengine.cpp +++ b/dataengines/filebrowser/filebrowserengine.cpp @@ -1,168 +1,157 @@ /* * Copyright (C) 2007 Ivan Cukic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License either version 2, or * (at your option) any later version as published by the Free Software * Foundation. * * 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, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "filebrowserengine.h" #include #include #include #include -#include #define InvalidIfEmpty(A) ((A.isEmpty())?(QVariant()):(QVariant(A))) #define forMatchingSources for (DataEngine::SourceDict::iterator it = sources.begin(); it != sources.end(); ++it) \ if (dir == QDir(it.key())) FileBrowserEngine::FileBrowserEngine(QObject* parent, const QVariantList& args) : Plasma::DataEngine(parent, args), m_dirWatch(nullptr) { Q_UNUSED(args) m_dirWatch = new KDirWatch(this); connect(m_dirWatch, &KDirWatch::created, this, &FileBrowserEngine::dirCreated); connect(m_dirWatch, &KDirWatch::deleted, this, &FileBrowserEngine::dirDeleted); connect(m_dirWatch, &KDirWatch::dirty, this, &FileBrowserEngine::dirDirty); } FileBrowserEngine::~FileBrowserEngine() { delete m_dirWatch; } void FileBrowserEngine::init() { qDebug() << "init() called"; } bool FileBrowserEngine::sourceRequestEvent(const QString &path) { qDebug() << "source requested() called: "<< path; m_dirWatch->addDir(path); setData(path, QStringLiteral("type"), QVariant("unknown")); updateData (path, INIT); return true; } void FileBrowserEngine::dirDirty(const QString &path) { updateData(path, DIRTY); } void FileBrowserEngine::dirCreated(const QString &path) { updateData(path, CREATED); } void FileBrowserEngine::dirDeleted(const QString &path) { updateData(path, DELETED); } void FileBrowserEngine::updateData(const QString &path, EventType event) { Q_UNUSED(event) ObjectType type = NOTHING; if (QDir(path).exists()) { type = DIRECTORY; } else if (QFile::exists(path)) { type = FILE; } DataEngine::SourceDict sources = containerDict(); QDir dir(path); clearData(path); if (type == DIRECTORY) { qDebug() << "directory info processing: "<< path; if (dir.isReadable()) { const QStringList visibleFiles = dir.entryList(QDir::Files, QDir::Name); const QStringList allFiles = dir.entryList(QDir::Files | QDir::Hidden, QDir::Name); const QStringList visibleDirectories = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name); const QStringList allDirectories = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden, QDir::Name); forMatchingSources { qDebug() << "MATCH"; it.value()->setData(QStringLiteral("item.type"), QVariant("directory")); QVariant vdTmp; if (!visibleDirectories.isEmpty()) vdTmp = QVariant(visibleDirectories); it.value()->setData(QStringLiteral("directories.visible"), vdTmp); QVariant adTmp; if (!allDirectories.empty()) adTmp = QVariant(allDirectories); it.value()->setData(QStringLiteral("directories.all"), adTmp); QVariant vfTmp; if (!visibleFiles.empty()) vfTmp = QVariant(visibleFiles); it.value()->setData(QStringLiteral("files.visible"), vfTmp); QVariant afTmp; if (!allFiles.empty()) afTmp = QVariant(allFiles); it.value()->setData(QStringLiteral("files.all"), afTmp); } } } else if (type == FILE) { qDebug() << "file info processing: "<< path; - KFileMetaInfo kfmi(path, QString(), KFileMetaInfo::Everything); - if (kfmi.isValid()) { - qDebug() << "METAINFO: " << kfmi.keys(); - - forMatchingSources { - qDebug() << "MATCH"; - it.value()->setData(QStringLiteral("item.type"), QVariant("file")); - - for (QHash< QString, KFileMetaInfoItem >::const_iterator i = kfmi.items().constBegin(); i != kfmi.items().constEnd(); ++i) { - it.value()->setData(i.key(), i.value().value()); - } - } + forMatchingSources { + it.value()->setData(QStringLiteral("item.type"), QVariant("file")); } } else { forMatchingSources { it.value()->setData(QStringLiteral("item.type"), QVariant("imaginary")); } }; } void FileBrowserEngine::clearData(const QString &path) { QDir dir(path); const DataEngine::SourceDict sources = containerDict(); for (DataEngine::SourceDict::const_iterator it = sources.begin(); it != sources.end(); ++it) { if (dir == QDir(it.key())) { qDebug() << "matched: "<< path << " "<< it.key(); it.value()->removeAllData(); } else { qDebug() << "didn't match: "<< path << " "<< it.key(); } } } K_EXPORT_PLASMA_DATAENGINE_WITH_JSON(filebrowser, FileBrowserEngine, "plasma-dataengine-filebrowser.json") #include "filebrowserengine.moc" diff --git a/klipper/CMakeLists.txt b/klipper/CMakeLists.txt index f90e1774b..71345ae5f 100644 --- a/klipper/CMakeLists.txt +++ b/klipper/CMakeLists.txt @@ -1,109 +1,112 @@ set(KLIPPER_VERSION_STRING ${PROJECT_VERSION}) add_definitions(-DTRANSLATION_DOMAIN=\"klipper\") add_definitions("-DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII") add_definitions(-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT) add_definitions(-DQT_NO_URL_CAST_FROM_STRING) set(libklipper_common_SRCS klipper.cpp urlgrabber.cpp configdialog.cpp history.cpp historyitem.cpp historymodel.cpp historystringitem.cpp klipperpopup.cpp popupproxy.cpp historyimageitem.cpp historyurlitem.cpp actionstreewidget.cpp editactiondialog.cpp clipcommandprocess.cpp ) ecm_qt_declare_logging_category(libklipper_common_SRCS HEADER klipper_debug.h IDENTIFIER KLIPPER_LOG CATEGORY_NAME org.kde.klipper) find_package(KF5Prison ${KF5_MIN_VERSION}) set_package_properties(KF5Prison PROPERTIES DESCRIPTION "Prison library" URL "http://projects.kde.org/prison" TYPE OPTIONAL PURPOSE "Needed to create mobile barcodes from clipboard data" ) set(HAVE_PRISON ${KF5Prison_FOUND}) configure_file(config-klipper.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-klipper.h ) -kde4_add_app_icon(libklipper_common_SRCS "${KDE4_ICON_INSTALL_DIR}/oxygen/*/apps/klipper.png") ki18n_wrap_ui(libklipper_common_SRCS generalconfig.ui actionsconfig.ui editactiondialog.ui) kconfig_add_kcfg_files(libklipper_common_SRCS klippersettings.kcfgc) set(klipper_KDEINIT_SRCS ${libklipper_common_SRCS} main.cpp tray.cpp) kf5_add_kdeinit_executable(klipper ${klipper_KDEINIT_SRCS}) target_link_libraries(kdeinit_klipper Qt5::Concurrent KF5::ConfigGui KF5::CoreAddons KF5::DBusAddons KF5::GlobalAccel KF5::IconThemes KF5::KIOWidgets KF5::Notifications KF5::Service KF5::TextWidgets KF5::WindowSystem KF5::WidgetsAddons KF5::XmlGui ${ZLIB_LIBRARY} ) if (X11_FOUND) target_link_libraries(kdeinit_klipper XCB::XCB Qt5::X11Extras) endif() if (HAVE_PRISON) target_link_libraries(kdeinit_klipper KF5::Prison) endif () install(TARGETS kdeinit_klipper ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(TARGETS klipper ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(PROGRAMS org.kde.klipper.desktop DESTINATION ${KDE_INSTALL_APPDIR}) install(PROGRAMS klipper.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR}) # Plasma Data Engine set(plasma_engine_clipboard_SRCS ${libklipper_common_SRCS} clipboardengine.cpp clipboardservice.cpp clipboardjob.cpp) add_library(plasma_engine_clipboard MODULE ${plasma_engine_clipboard_SRCS}) kcoreaddons_desktop_to_json(plasma_engine_clipboard plasma-dataengine-clipboard.desktop) target_link_libraries(plasma_engine_clipboard Qt5::Concurrent Qt5::DBus Qt5::Widgets # QAction KF5::ConfigGui KF5::CoreAddons # KUrlMimeData KF5::GlobalAccel KF5::IconThemes KF5::KIOWidgets # PreviewJob KF5::Plasma KF5::Notifications KF5::Service KF5::TextWidgets # KTextEdit KF5::WidgetsAddons # KMessageBox KF5::WindowSystem KF5::XmlGui # KActionCollection ${ZLIB_LIBRARY} ) if (X11_FOUND) target_link_libraries(plasma_engine_clipboard XCB::XCB Qt5::X11Extras) endif() if (HAVE_PRISON) target_link_libraries(plasma_engine_clipboard KF5::Prison) endif () install(TARGETS plasma_engine_clipboard DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasma/dataengine) install(FILES plasma-dataengine-clipboard.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) install(FILES org.kde.plasma.clipboard.operations DESTINATION ${PLASMA_DATA_INSTALL_DIR}/services) if(BUILD_TESTING) add_subdirectory(autotests) endif() -install( FILES klipper.categories DESTINATION ${KDE_INSTALL_CONFDIR} ) +if (${ECM_VERSION} STRGREATER "5.58.0") + install( FILES klipper.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR} ) +else() + install( FILES klipper.categories DESTINATION ${KDE_INSTALL_CONFDIR} ) +endif() diff --git a/ksmserver/main.cpp b/ksmserver/main.cpp index 0467a5d14..d72bc1859 100644 --- a/ksmserver/main.cpp +++ b/ksmserver/main.cpp @@ -1,351 +1,345 @@ /***************************************************************** ksmserver - the KDE session management server Copyright 2000 Matthias Ettrich Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "server.h" #include "startup.h" #include "shutdown.h" #include #include #include #include #include static const char version[] = "0.4"; static const char description[] = I18N_NOOP( "The reliable KDE session manager that talks the standard X11R6 \nsession management protocol (XSMP)." ); Display* dpy = nullptr; Colormap colormap = 0; Visual *visual = nullptr; extern KSMServer* the_server; void IoErrorHandler ( IceConn iceConn) { the_server->ioError( iceConn ); } bool writeTest(QByteArray path) { path += "/XXXXXX"; int fd = mkstemp(path.data()); if (fd == -1) return false; if (write(fd, "Hello World\n", 12) == -1) { int save_errno = errno; close(fd); unlink(path.data()); errno = save_errno; return false; } close(fd); unlink(path.data()); return true; } void checkComposite() { if( qgetenv( "KDE_SKIP_ARGB_VISUALS" ) == "1" ) return; // thanks to zack rusin and frederik for pointing me in the right direction // for the following bits of X11 code dpy = XOpenDisplay(nullptr); // open default display if (!dpy) { qCCritical(KSMSERVER) << "Cannot connect to the X server"; return; } int screen = DefaultScreen(dpy); int eventBase, errorBase; if (XRenderQueryExtension(dpy, &eventBase, &errorBase)) { int nvi; XVisualInfo templ; templ.screen = screen; templ.depth = 32; templ.c_class = TrueColor; XVisualInfo *xvi = XGetVisualInfo(dpy, VisualScreenMask | VisualDepthMask | VisualClassMask, &templ, &nvi); for (int i = 0; i < nvi; ++i) { XRenderPictFormat *format = XRenderFindVisualFormat(dpy, xvi[i].visual); if (format->type == PictTypeDirect && format->direct.alphaMask) { visual = xvi[i].visual; colormap = XCreateColormap(dpy, RootWindow(dpy, screen), visual, AllocNone); XFree(xvi); return; } } XFree(xvi); } XCloseDisplay( dpy ); dpy = nullptr; } void sanity_check( int argc, char* argv[] ) { QString msg; QByteArray path = qgetenv("HOME"); const QByteArray readOnly = qgetenv("KDE_HOME_READONLY"); if (path.isEmpty()) { msg = i18n("$HOME not set!"); } if (msg.isEmpty() && access(path.data(), W_OK)) { if (errno == ENOENT) msg = i18n("$HOME directory (%1) does not exist.", QFile::decodeName(path)); else if (readOnly.isEmpty()) msg = i18n("No write access to $HOME directory (%1).", QFile::decodeName(path)); } if (msg.isEmpty() && access(path.data(), R_OK)) { if (errno == ENOENT) msg = i18n("$HOME directory (%1) does not exist.", QFile::decodeName(path)); else msg = i18n("No read access to $HOME directory (%1).", QFile::decodeName(path)); } if (msg.isEmpty() && readOnly.isEmpty() && !writeTest(path)) { if (errno == ENOSPC) msg = i18n("$HOME directory (%1) is out of disk space.", QFile::decodeName(path)); else msg = i18n("Writing to the $HOME directory (%2) failed with " "the error '%1'", QString::fromLocal8Bit(strerror(errno)), QFile::decodeName(path)); } if (msg.isEmpty()) { path = getenv("ICEAUTHORITY"); if (path.isEmpty()) { path = qgetenv("HOME"); path += "/.ICEauthority"; } if (access(path.data(), W_OK) && (errno != ENOENT)) msg = i18n("No write access to '%1'.", QFile::decodeName(path)); else if (access(path.data(), R_OK) && (errno != ENOENT)) msg = i18n("No read access to '%1'.", QFile::decodeName(path)); } if (msg.isEmpty()) { path = getenv("KDETMP"); if (path.isEmpty()) path = "/tmp"; if (!writeTest(path)) { if (errno == ENOSPC) msg = i18n("Temp directory (%1) is out of disk space.", QFile::decodeName(path)); else msg = i18n("Writing to the temp directory (%2) failed with\n " "the error '%1'", QString::fromLocal8Bit(strerror(errno)), QFile::decodeName(path)); } } if (msg.isEmpty() && (path != "/tmp")) { path = "/tmp"; if (!writeTest(path)) { if (errno == ENOSPC) msg = i18n("Temp directory (%1) is out of disk space."); else msg = i18n("Writing to the temp directory (%2) failed with\n " "the error '%1'", QString::fromLocal8Bit(strerror(errno)), QFile::decodeName(path)); } } if (msg.isEmpty()) { path += "/.ICE-unix"; if (access(path.data(), W_OK) && (errno != ENOENT)) msg = i18n("No write access to '%1'."); else if (access(path.data(), R_OK) && (errno != ENOENT)) msg = i18n("No read access to '%1'."); } if (!msg.isEmpty()) { const QString msg_pre = i18n("The following installation problem was detected\n" "while trying to start KDE:") + QStringLiteral("\n\n "); const QString msg_post = i18n("\n\nKDE is unable to start.\n"); fputs(msg_pre.toUtf8().constData(), stderr); fprintf(stderr, "%s", msg.toUtf8().constData()); fputs(msg_post.toUtf8().constData(), stderr); QApplication a(argc, argv); const QString qmsg = msg_pre + msg + msg_post; KMessageBox::error(nullptr, qmsg, i18n("Plasma Workspace installation problem!")); exit(255); } } extern "C" Q_DECL_EXPORT int kdemain( int argc, char* argv[] ) { sanity_check(argc, argv); putenv((char*)"SESSION_MANAGER="); checkComposite(); // force xcb QPA plugin as ksmserver is very X11 specific const QByteArray origQpaPlatform = qgetenv("QT_QPA_PLATFORM"); qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("xcb")); QQuickWindow::setDefaultAlphaBuffer(true); QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling); QApplication *a = new QApplication(argc, argv); // now the QPA platform is set, unset variable again to not launch apps with incorrect environment if (origQpaPlatform.isEmpty()) { qunsetenv("QT_QPA_PLATFORM"); } else { qputenv("QT_QPA_PLATFORM", origQpaPlatform); } QApplication::setApplicationName( QStringLiteral( "ksmserver") ); QApplication::setApplicationVersion( QString::fromLatin1( version ) ); QApplication::setOrganizationDomain( QStringLiteral( "kde.org") ); fcntl(ConnectionNumber(QX11Info::display()), F_SETFD, 1); a->setQuitOnLastWindowClosed(false); // #169486 QCommandLineParser parser; parser.setApplicationDescription(i18n(description)); parser.addHelpOption(); parser.addVersionOption(); QCommandLineOption restoreOption(QStringList() << QStringLiteral("r") << QStringLiteral("restore"), i18n("Restores the saved user session if available")); parser.addOption(restoreOption); QCommandLineOption wmOption(QStringList() << QStringLiteral("w") << QStringLiteral("windowmanager"), i18n("Starts in case no other window manager is \nparticipating in the session. Default is 'kwin'"), i18n("wm")); parser.addOption(wmOption); QCommandLineOption nolocalOption(QStringLiteral("nolocal"), i18n("Also allow remote connections")); parser.addOption(nolocalOption); QCommandLineOption lockscreenOption(QStringLiteral("lockscreen"), i18n("Starts the session in locked mode")); parser.addOption(lockscreenOption); QCommandLineOption noLockscreenOption(QStringLiteral("no-lockscreen"), i18n("Starts without lock screen support. Only needed if other component provides the lock screen.")); parser.addOption(noLockscreenOption); parser.process(*a); QString wm = parser.value(wmOption); bool only_local = !parser.isSet(nolocalOption); #ifndef HAVE__ICETRANSNOLISTEN /* this seems strange, but the default is only_local, so if !only_local * the option --nolocal was given, and we warn (the option --nolocal * does nothing on this platform, as here the default is reversed) */ if (!only_local) { qCWarning(KSMSERVER, "--nolocal is not supported on your platform. Sorry."); } only_local = false; #endif KSMServer::InitFlags flags = KSMServer::InitFlag::None; if (only_local) { flags |= KSMServer::InitFlag::OnlyLocal; } if (parser.isSet(lockscreenOption)) { flags |= KSMServer::InitFlag::ImmediateLockScreen; } if (parser.isSet(noLockscreenOption)) { flags |= KSMServer::InitFlag::NoLockScreen; } KSMServer *server = new KSMServer( wm, flags); auto startup = new Startup(server); new Shutdown(a); // for the KDE-already-running check in startkde KSelectionOwner kde_running( "_KDE_RUNNING", 0 ); kde_running.claim( false ); IceSetIOErrorHandler( IoErrorHandler ); KConfigGroup config(KSharedConfig::openConfig(), "General"); - int realScreenCount = ScreenCount( QX11Info::display() ); - bool screenCountChanged = - ( config.readEntry( "screenCount", realScreenCount ) != realScreenCount ); - QString loginMode = config.readEntry( "loginMode", "restorePreviousLogout" ); - if ( parser.isSet( restoreOption ) && ! screenCountChanged ) + if ( parser.isSet( restoreOption )) server->restoreSession( QStringLiteral( SESSION_BY_USER ) ); - else if ( loginMode == QStringLiteral( "default" ) || screenCountChanged ) - server->startDefaultSession(); else if ( loginMode == QStringLiteral( "restorePreviousLogout" ) ) server->restoreSession( QStringLiteral( SESSION_PREVIOUS_LOGOUT ) ); else if ( loginMode == QStringLiteral( "restoreSavedSession" ) ) server->restoreSession( QStringLiteral( SESSION_BY_USER ) ); else server->startDefaultSession(); startup->upAndRunning(QStringLiteral( "ksmserver" )); KDBusService service(KDBusService::Unique); int ret = a->exec(); kde_running.release(); // needs to be done before QApplication destruction delete a; return ret; } diff --git a/ksmserver/server.cpp b/ksmserver/server.cpp index f3072b82e..4d30ea599 100644 --- a/ksmserver/server.cpp +++ b/ksmserver/server.cpp @@ -1,1220 +1,1219 @@ /***************************************************************** ksmserver - the KDE session management server Copyright 2000 Matthias Ettrich Copyright 2005 Lubos Lunak relatively small extensions by Oswald Buddenhagen some code taken from the dcopserver (part of the KDE libraries), which is Copyright 1999 Matthias Ettrich Copyright 1999 Preston Brown Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************/ #include "server.h" #include "global.h" #include "client.h" #include "ksmserver_debug.h" #include "ksmserverinterfaceadaptor.h" #include "klocalizedstring.h" #include "kglobalaccel.h" #include #include // HAVE_LIMITS_H #include #include #include #include #include #ifdef HAVE_SYS_TIME_H #include #endif #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIMITS_H #include #endif #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 "kscreenlocker_interface.h" KSMServer* the_server = nullptr; KSMServer* KSMServer::self() { return the_server; } /*! Utility function to execute a command on the local machine. Used * to restart applications. */ KProcess* KSMServer::startApplication( const QStringList& cmd, const QString& clientMachine, const QString& userId, bool wm ) { QStringList command = cmd; if ( command.isEmpty() ) return nullptr; if ( !userId.isEmpty()) { struct passwd* pw = getpwuid( getuid()); if( pw != nullptr && userId != QString::fromLocal8Bit( pw->pw_name )) { command.prepend( QStringLiteral("--") ); command.prepend( userId ); command.prepend( QStringLiteral("-u") ); command.prepend( QStandardPaths::findExecutable(QStringLiteral("kdesu"))); } } if ( !clientMachine.isEmpty() && clientMachine != QStringLiteral("localhost") ) { command.prepend( clientMachine ); command.prepend( xonCommand ); // "xon" by default } // TODO this function actually should not use KProcess at all and use klauncher (kdeinit) instead. // Klauncher should also have support for tracking whether the launched process is still alive // or not, so this should be redone. For now, use KProcess for wm's, as they need to be tracked, // klauncher for the rest where ksmserver doesn't care. if( wm ) { KProcess* process = new KProcess( this ); *process << command; // make it auto-delete connect(process, static_cast(&KProcess::error), process, &KProcess::deleteLater); connect(process, static_cast(&KProcess::finished), process, &KProcess::deleteLater); process->start(); return process; } else { int n = command.count(); org::kde::KLauncher klauncher(QStringLiteral("org.kde.klauncher5"), QStringLiteral("/KLauncher"), QDBusConnection::sessionBus()); QString app = command[0]; QStringList argList; for ( int i=1; i < n; i++) argList.append( command[i]); klauncher.exec_blind(app, argList ); return nullptr; } } /*! Utility function to execute a command on the local machine. Used * to discard session data */ void KSMServer::executeCommand( const QStringList& command ) { if ( command.isEmpty() ) return; KProcess::execute( command ); } IceAuthDataEntry *authDataEntries = nullptr; static QTemporaryFile *remTempFile = nullptr; static IceListenObj *listenObjs = nullptr; int numTransports = 0; static bool only_local = 0; static Bool HostBasedAuthProc ( char* /*hostname*/) { if (only_local) return true; else return false; } Status KSMRegisterClientProc ( SmsConn /* smsConn */, SmPointer managerData, char * previousId ) { KSMClient* client = (KSMClient*) managerData; client->registerClient( previousId ); return 1; } void KSMInteractRequestProc ( SmsConn /* smsConn */, SmPointer managerData, int dialogType ) { the_server->interactRequest( (KSMClient*) managerData, dialogType ); } void KSMInteractDoneProc ( SmsConn /* smsConn */, SmPointer managerData, Bool cancelShutdown ) { the_server->interactDone( (KSMClient*) managerData, cancelShutdown ); } void KSMSaveYourselfRequestProc ( SmsConn smsConn , SmPointer /* managerData */, int saveType, Bool shutdown, int interactStyle, Bool fast, Bool global ) { if ( shutdown ) { the_server->shutdown( fast ? KWorkSpace::ShutdownConfirmNo : KWorkSpace::ShutdownConfirmDefault, KWorkSpace::ShutdownTypeDefault, KWorkSpace::ShutdownModeDefault ); } else if ( !global ) { SmsSaveYourself( smsConn, saveType, false, interactStyle, fast ); SmsSaveComplete( smsConn ); } // else checkpoint only, ksmserver does not yet support this // mode. Will come for KDE 3.1 } void KSMSaveYourselfPhase2RequestProc ( SmsConn /* smsConn */, SmPointer managerData ) { the_server->phase2Request( (KSMClient*) managerData ); } void KSMSaveYourselfDoneProc ( SmsConn /* smsConn */, SmPointer managerData, Bool success ) { the_server->saveYourselfDone( (KSMClient*) managerData, success ); } void KSMCloseConnectionProc ( SmsConn smsConn, SmPointer managerData, int count, char ** reasonMsgs ) { the_server->deleteClient( ( KSMClient* ) managerData ); if ( count ) SmFreeReasons( count, reasonMsgs ); IceConn iceConn = SmsGetIceConnection( smsConn ); SmsCleanUp( smsConn ); IceSetShutdownNegotiation (iceConn, False); IceCloseConnection( iceConn ); } void KSMSetPropertiesProc ( SmsConn /* smsConn */, SmPointer managerData, int numProps, SmProp ** props ) { KSMClient* client = ( KSMClient* ) managerData; for ( int i = 0; i < numProps; i++ ) { SmProp *p = client->property( props[i]->name ); if ( p ) { client->properties.removeAll( p ); SmFreeProperty( p ); } client->properties.append( props[i] ); if ( !qstrcmp( props[i]->name, SmProgram ) ) the_server->clientSetProgram( client ); } if ( numProps ) free( props ); } void KSMDeletePropertiesProc ( SmsConn /* smsConn */, SmPointer managerData, int numProps, char ** propNames ) { KSMClient* client = ( KSMClient* ) managerData; for ( int i = 0; i < numProps; i++ ) { SmProp *p = client->property( propNames[i] ); if ( p ) { client->properties.removeAll( p ); SmFreeProperty( p ); } } } void KSMGetPropertiesProc ( SmsConn smsConn, SmPointer managerData ) { KSMClient* client = ( KSMClient* ) managerData; SmProp** props = new SmProp*[client->properties.count()]; int i = 0; foreach( SmProp *prop, client->properties ) props[i++] = prop; SmsReturnProperties( smsConn, i, props ); delete [] props; } class KSMListener : public QSocketNotifier { public: KSMListener( IceListenObj obj ) : QSocketNotifier( IceGetListenConnectionNumber( obj ), QSocketNotifier::Read ) { listenObj = obj; } IceListenObj listenObj; }; class KSMConnection : public QSocketNotifier { public: KSMConnection( IceConn conn ) : QSocketNotifier( IceConnectionNumber( conn ), QSocketNotifier::Read ) { iceConn = conn; } IceConn iceConn; }; /* for printing hex digits */ static void fprintfhex (FILE *fp, unsigned int len, char *cp) { static const char hexchars[] = "0123456789abcdef"; for (; len > 0; len--, cp++) { unsigned char s = *cp; putc(hexchars[s >> 4], fp); putc(hexchars[s & 0x0f], fp); } } /* * We use temporary files which contain commands to add/remove entries from * the .ICEauthority file. */ static void write_iceauth (FILE *addfp, FILE *removefp, IceAuthDataEntry *entry) { fprintf (addfp, "add %s \"\" %s %s ", entry->protocol_name, entry->network_id, entry->auth_name); fprintfhex (addfp, entry->auth_data_length, entry->auth_data); fprintf (addfp, "\n"); fprintf (removefp, "remove protoname=%s protodata=\"\" netid=%s authname=%s\n", entry->protocol_name, entry->network_id, entry->auth_name); } #define MAGIC_COOKIE_LEN 16 Status SetAuthentication_local (int count, IceListenObj *listenObjs) { int i; for (i = 0; i < count; i ++) { char *prot = IceGetListenConnectionString(listenObjs[i]); if (!prot) continue; char *host = strchr(prot, '/'); char *sock = nullptr; if (host) { *host=0; host++; sock = strchr(host, ':'); if (sock) { *sock = 0; sock++; } } qCDebug(KSMSERVER) << "KSMServer: SetAProc_loc: conn " << (unsigned)i << ", prot=" << prot << ", file=" << sock; if (sock && !strcmp(prot, "local")) { chmod(sock, 0700); } IceSetHostBasedAuthProc (listenObjs[i], HostBasedAuthProc); free(prot); } return 1; } Status SetAuthentication (int count, IceListenObj *listenObjs, IceAuthDataEntry **authDataEntries) { QTemporaryFile addTempFile; remTempFile = new QTemporaryFile; if (!addTempFile.open() || !remTempFile->open()) return 0; if ((*authDataEntries = (IceAuthDataEntry *) malloc ( count * 2 * sizeof (IceAuthDataEntry))) == nullptr) return 0; FILE *addAuthFile = fopen(QFile::encodeName(addTempFile.fileName()).constData(), "r+"); FILE *remAuthFile = fopen(QFile::encodeName(remTempFile->fileName()).constData(), "r+"); for (int i = 0; i < numTransports * 2; i += 2) { (*authDataEntries)[i].network_id = IceGetListenConnectionString (listenObjs[i/2]); (*authDataEntries)[i].protocol_name = (char *) "ICE"; (*authDataEntries)[i].auth_name = (char *) "MIT-MAGIC-COOKIE-1"; (*authDataEntries)[i].auth_data = IceGenerateMagicCookie (MAGIC_COOKIE_LEN); (*authDataEntries)[i].auth_data_length = MAGIC_COOKIE_LEN; (*authDataEntries)[i+1].network_id = IceGetListenConnectionString (listenObjs[i/2]); (*authDataEntries)[i+1].protocol_name = (char *) "XSMP"; (*authDataEntries)[i+1].auth_name = (char *) "MIT-MAGIC-COOKIE-1"; (*authDataEntries)[i+1].auth_data = IceGenerateMagicCookie (MAGIC_COOKIE_LEN); (*authDataEntries)[i+1].auth_data_length = MAGIC_COOKIE_LEN; write_iceauth (addAuthFile, remAuthFile, &(*authDataEntries)[i]); write_iceauth (addAuthFile, remAuthFile, &(*authDataEntries)[i+1]); IceSetPaAuthData (2, &(*authDataEntries)[i]); IceSetHostBasedAuthProc (listenObjs[i/2], HostBasedAuthProc); } fclose(addAuthFile); fclose(remAuthFile); QString iceAuth = QStandardPaths::findExecutable(QStringLiteral("iceauth")); if (iceAuth.isEmpty()) { qCWarning(KSMSERVER, "KSMServer: could not find iceauth"); return 0; } KProcess p; p << iceAuth << QStringLiteral("source") << addTempFile.fileName(); p.execute(); return (1); } /* * Free up authentication data. */ void FreeAuthenticationData(int count, IceAuthDataEntry *authDataEntries) { /* Each transport has entries for ICE and XSMP */ if (only_local) return; for (int i = 0; i < count * 2; i++) { free (authDataEntries[i].network_id); free (authDataEntries[i].auth_data); } free (authDataEntries); QString iceAuth = QStandardPaths::findExecutable(QStringLiteral("iceauth")); if (iceAuth.isEmpty()) { qCWarning(KSMSERVER, "KSMServer: could not find iceauth"); return; } if (remTempFile) { KProcess p; p << iceAuth << QStringLiteral("source") << remTempFile->fileName(); p.execute(); } delete remTempFile; remTempFile = nullptr; } static int Xio_ErrorHandler( Display * ) { qCWarning(KSMSERVER, "ksmserver: Fatal IO error: client killed"); // Don't do anything that might require the X connection if (the_server) { KSMServer *server = the_server; the_server = nullptr; server->cleanUp(); // Don't delete server!! } exit(0); // Don't report error, it's not our fault. return 0; // Bogus return value, notreached } void KSMServer::setupXIOErrorHandler() { XSetIOErrorHandler(Xio_ErrorHandler); } static int wake_up_socket = -1; static void sighandler(int sig) { if (sig == SIGHUP) { signal(SIGHUP, sighandler); return; } char ch = 0; (void)::write(wake_up_socket, &ch, 1); } void KSMWatchProc ( IceConn iceConn, IcePointer client_data, Bool opening, IcePointer* watch_data) { KSMServer* ds = ( KSMServer*) client_data; if (opening) { *watch_data = (IcePointer) ds->watchConnection( iceConn ); } else { ds->removeConnection( (KSMConnection*) *watch_data ); } } static Status KSMNewClientProc ( SmsConn conn, SmPointer manager_data, unsigned long* mask_ret, SmsCallbacks* cb, char** failure_reason_ret) { *failure_reason_ret = nullptr; void* client = ((KSMServer*) manager_data )->newClient( conn ); cb->register_client.callback = KSMRegisterClientProc; cb->register_client.manager_data = client; cb->interact_request.callback = KSMInteractRequestProc; cb->interact_request.manager_data = client; cb->interact_done.callback = KSMInteractDoneProc; cb->interact_done.manager_data = client; cb->save_yourself_request.callback = KSMSaveYourselfRequestProc; cb->save_yourself_request.manager_data = client; cb->save_yourself_phase2_request.callback = KSMSaveYourselfPhase2RequestProc; cb->save_yourself_phase2_request.manager_data = client; cb->save_yourself_done.callback = KSMSaveYourselfDoneProc; cb->save_yourself_done.manager_data = client; cb->close_connection.callback = KSMCloseConnectionProc; cb->close_connection.manager_data = client; cb->set_properties.callback = KSMSetPropertiesProc; cb->set_properties.manager_data = client; cb->delete_properties.callback = KSMDeletePropertiesProc; cb->delete_properties.manager_data = client; cb->get_properties.callback = KSMGetPropertiesProc; cb->get_properties.manager_data = client; *mask_ret = SmsRegisterClientProcMask | SmsInteractRequestProcMask | SmsInteractDoneProcMask | SmsSaveYourselfRequestProcMask | SmsSaveYourselfP2RequestProcMask | SmsSaveYourselfDoneProcMask | SmsCloseConnectionProcMask | SmsSetPropertiesProcMask | SmsDeletePropertiesProcMask | SmsGetPropertiesProcMask; return 1; } #ifdef HAVE__ICETRANSNOLISTEN extern "C" int _IceTransNoListen(const char * protocol); #endif KSMServer::KSMServer( const QString& windowManager, InitFlags flags ) : wmProcess( nullptr ) , sessionGroup( QStringLiteral( "" ) ) , logoutEffectWidget( nullptr ) , sockets{ -1, -1 } { if (!flags.testFlag(InitFlag::NoLockScreen)) { ScreenLocker::KSldApp::self()->initialize(); if (flags.testFlag(InitFlag::ImmediateLockScreen)) { ScreenLocker::KSldApp::self()->lock(ScreenLocker::EstablishLock::Immediate); } } if(::socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sockets) != 0) qFatal("Could not create socket pair, error %d (%s)", errno, strerror(errno)); wake_up_socket = sockets[0]; QSocketNotifier* n = new QSocketNotifier(sockets[1], QSocketNotifier::Read, this); qApp->connect(n, &QSocketNotifier::activated, &QApplication::quit); new KSMServerInterfaceAdaptor( this ); QDBusConnection::sessionBus().registerObject(QStringLiteral("/KSMServer"), this); the_server = this; clean = false; state = Idle; saveSession = false; wmPhase1WaitingCount = 0; KConfigGroup config(KSharedConfig::openConfig(), "General"); clientInteracting = nullptr; xonCommand = config.readEntry( "xonCommand", "xon" ); if (windowManager.isEmpty()) { wm = QStringLiteral(KWIN_BIN); } else { wm = windowManager; } wmCommands = QStringList({wm}); only_local = flags.testFlag(InitFlag::OnlyLocal); #ifdef HAVE__ICETRANSNOLISTEN if (only_local) _IceTransNoListen("tcp"); #else only_local = false; #endif char errormsg[256]; if (!SmsInitialize ( (char*) KSMVendorString, (char*) KSMReleaseString, KSMNewClientProc, (SmPointer) this, HostBasedAuthProc, 256, errormsg ) ) { qCWarning(KSMSERVER, "KSMServer: could not register XSM protocol"); } if (!IceListenForConnections (&numTransports, &listenObjs, 256, errormsg)) { qCWarning(KSMSERVER, "KSMServer: Error listening for connections: %s", errormsg); qCWarning(KSMSERVER, "KSMServer: Aborting."); exit(1); } { // publish available transports. QByteArray fName = QFile::encodeName(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + QDir::separator() + QStringLiteral("KSMserver")); qCDebug(KSMSERVER) << fName; QString display = QString::fromLocal8Bit(::getenv("DISPLAY")); // strip the screen number from the display display.remove(QRegExp(QStringLiteral("\\.[0-9]+$"))); int i; while( (i = display.indexOf(QLatin1Char(':'))) >= 0) display[i] = '_'; while( (i = display.indexOf(QLatin1Char('/'))) >= 0) display[i] = '_'; fName += '_'+display.toLocal8Bit(); FILE *f; f = ::fopen(fName.data(), "w+"); if (!f) { qCWarning(KSMSERVER, "KSMServer: cannot open %s: %s", fName.data(), strerror(errno)); qCWarning(KSMSERVER, "KSMServer: Aborting."); exit(1); } char* session_manager = IceComposeNetworkIdList(numTransports, listenObjs); fprintf(f, "%s\n%i\n", session_manager, getpid()); fclose(f); setenv( "SESSION_MANAGER", session_manager, true ); // Pass env. var to kdeinit. org::kde::KLauncher klauncher( QStringLiteral( "org.kde.klauncher5" ), QStringLiteral( "/KLauncher" ), QDBusConnection::sessionBus()); klauncher.setLaunchEnv( QStringLiteral( "SESSION_MANAGER" ), QString::fromLocal8Bit( (const char*) session_manager ) ); free(session_manager); } if (only_local) { if (!SetAuthentication_local(numTransports, listenObjs)) qFatal("KSMSERVER: authentication setup failed."); } else { if (!SetAuthentication(numTransports, listenObjs, &authDataEntries)) qFatal("KSMSERVER: authentication setup failed."); } IceAddConnectionWatch (KSMWatchProc, (IcePointer) this); KSMListener* con; for ( int i = 0; i < numTransports; i++) { fcntl( IceGetListenConnectionNumber( listenObjs[i] ), F_SETFD, FD_CLOEXEC ); con = new KSMListener( listenObjs[i] ); listener.append( con ); connect(con, &KSMListener::activated, this, &KSMServer::newConnection); } signal(SIGHUP, sighandler); signal(SIGTERM, sighandler); signal(SIGINT, sighandler); signal(SIGPIPE, SIG_IGN); connect(&protectionTimer, &QTimer::timeout, this, &KSMServer::protectionTimeout); connect(&restoreTimer, &QTimer::timeout, this, &KSMServer::tryRestoreNext); connect(qApp, &QApplication::aboutToQuit, this, &KSMServer::cleanUp); } KSMServer::~KSMServer() { qDeleteAll( listener ); the_server = nullptr; cleanUp(); } void KSMServer::cleanUp() { if (clean) return; clean = true; IceFreeListenObjs (numTransports, listenObjs); wake_up_socket = -1; ::close(sockets[1]); ::close(sockets[0]); sockets[0] = -1; sockets[1] = -1; QByteArray fName = QFile::encodeName(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + QLatin1Char('/') + QStringLiteral("KSMserver")); QString display = QString::fromLocal8Bit(::getenv("DISPLAY")); // strip the screen number from the display display.remove(QRegExp(QStringLiteral("\\.[0-9]+$"))); int i; while( (i = display.indexOf(QLatin1Char(':'))) >= 0) display[i] = '_'; while( (i = display.indexOf(QLatin1Char('/'))) >= 0) display[i] = '_'; fName += '_'+display.toLocal8Bit(); ::unlink(fName.data()); FreeAuthenticationData(numTransports, authDataEntries); signal(SIGTERM, SIG_DFL); signal(SIGINT, SIG_DFL); } void* KSMServer::watchConnection( IceConn iceConn ) { KSMConnection* conn = new KSMConnection( iceConn ); connect(conn, &KSMConnection::activated, this, &KSMServer::processData); return (void*) conn; } void KSMServer::removeConnection( KSMConnection* conn ) { delete conn; } /*! Called from our IceIoErrorHandler */ void KSMServer::ioError( IceConn /*iceConn*/ ) { } void KSMServer::processData( int /*socket*/ ) { IceConn iceConn = ((KSMConnection*)sender())->iceConn; IceProcessMessagesStatus status = IceProcessMessages( iceConn, nullptr, nullptr ); if ( status == IceProcessMessagesIOError ) { IceSetShutdownNegotiation( iceConn, False ); QList::iterator it = clients.begin(); QList::iterator const itEnd = clients.end(); while ( ( it != itEnd ) && *it && ( SmsGetIceConnection( ( *it )->connection() ) != iceConn ) ) ++it; if ( ( it != itEnd ) && *it ) { SmsConn smsConn = (*it)->connection(); deleteClient( *it ); SmsCleanUp( smsConn ); } (void) IceCloseConnection( iceConn ); } } KSMClient* KSMServer::newClient( SmsConn conn ) { KSMClient* client = new KSMClient( conn ); clients.append( client ); return client; } void KSMServer::deleteClient( KSMClient* client ) { if ( !clients.contains( client ) ) // paranoia return; clients.removeAll( client ); clientsToKill.removeAll( client ); clientsToSave.removeAll( client ); if ( client == clientInteracting ) { clientInteracting = nullptr; handlePendingInteractions(); } delete client; if ( state == Shutdown || state == Checkpoint || state == ClosingSubSession ) completeShutdownOrCheckpoint(); if ( state == Killing ) completeKilling(); else if ( state == KillingSubSession ) completeKillingSubSession(); if ( state == KillingWM ) completeKillingWM(); } void KSMServer::newConnection( int /*socket*/ ) { IceAcceptStatus status; IceConn iceConn = IceAcceptConnection( ((KSMListener*)sender())->listenObj, &status); if( iceConn == nullptr ) return; IceSetShutdownNegotiation( iceConn, False ); IceConnectStatus cstatus; while ((cstatus = IceConnectionStatus (iceConn))==IceConnectPending) { (void) IceProcessMessages( iceConn, nullptr, nullptr ); } if (cstatus != IceConnectAccepted) { if (cstatus == IceConnectIOError) qCDebug(KSMSERVER) << "IO error opening ICE Connection!"; else qCDebug(KSMSERVER) << "ICE Connection rejected!"; (void )IceCloseConnection (iceConn); return; } // don't leak the fd fcntl( IceConnectionNumber(iceConn), F_SETFD, FD_CLOEXEC ); } QString KSMServer::currentSession() { if ( sessionGroup.startsWith( QLatin1String( "Session: " ) ) ) return sessionGroup.mid( 9 ); return QStringLiteral( "" ); // empty, not null, since used for KConfig::setGroup } void KSMServer::discardSession() { KConfigGroup config(KSharedConfig::openConfig(), sessionGroup ); int count = config.readEntry( "count", 0 ); foreach ( KSMClient *c, clients ) { QStringList discardCommand = c->discardCommand(); if ( discardCommand.isEmpty()) continue; // check that non of the old clients used the exactly same // discardCommand before we execute it. This used to be the // case up to KDE and Qt < 3.1 int i = 1; while ( i <= count && config.readPathEntry( QStringLiteral("discardCommand") + QString::number(i), QStringList() ) != discardCommand ) i++; if ( i <= count ) executeCommand( discardCommand ); } } void KSMServer::storeSession() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); config->reparseConfiguration(); // config may have changed in the KControl module KConfigGroup generalGroup(config, "General"); excludeApps = generalGroup.readEntry( "excludeApps" ).toLower() .split( QRegExp( QStringLiteral("[,:]") ), QString::SkipEmptyParts ); KConfigGroup configSessionGroup(config, sessionGroup); int count = configSessionGroup.readEntry( "count", 0 ); for ( int i = 1; i <= count; i++ ) { QStringList discardCommand = configSessionGroup.readPathEntry( QLatin1String("discardCommand") + QString::number(i), QStringList() ); if ( discardCommand.isEmpty()) continue; // check that non of the new clients uses the exactly same // discardCommand before we execute it. This used to be the // case up to KDE and Qt < 3.1 QList::iterator it = clients.begin(); QList::iterator const itEnd = clients.end(); while ( ( it != itEnd ) && *it && (discardCommand != ( *it )->discardCommand() ) ) ++it; if ( ( it != itEnd ) && *it ) continue; executeCommand( discardCommand ); } config->deleteGroup( sessionGroup ); //### does not work with global config object... KConfigGroup cg( config, sessionGroup); count = 0; if (state != ClosingSubSession) { // put the wm first foreach ( KSMClient *c, clients ) if ( c->program() == wm ) { clients.removeAll( c ); clients.prepend( c ); break; } } foreach ( KSMClient *c, clients ) { int restartHint = c->restartStyleHint(); if (restartHint == SmRestartNever) continue; QString program = c->program(); QStringList restartCommand = c->restartCommand(); if (program.isEmpty() && restartCommand.isEmpty()) continue; if (state == ClosingSubSession && ! clientsToSave.contains(c)) continue; // 'program' might be (mostly) fullpath, or (sometimes) just the name. // 'name' is just the name. QFileInfo info(program); const QString& name = info.fileName(); if ( excludeApps.contains(program.toLower()) || excludeApps.contains(name.toLower()) ) { continue; } count++; QString n = QString::number(count); cg.writeEntry( QStringLiteral("program")+n, program ); cg.writeEntry( QStringLiteral("clientId")+n, c->clientId() ); cg.writeEntry( QStringLiteral("restartCommand")+n, restartCommand ); cg.writePathEntry( QStringLiteral("discardCommand")+n, c->discardCommand() ); cg.writeEntry( QStringLiteral("restartStyleHint")+n, restartHint ); cg.writeEntry( QStringLiteral("userId")+n, c->userId() ); cg.writeEntry( QStringLiteral("wasWm")+n, isWM( c )); } cg.writeEntry( "count", count ); KConfigGroup cg2( config, "General"); - cg2.writeEntry( "screenCount", ScreenCount(QX11Info::display())); storeLegacySession(config.data()); config->sync(); } QStringList KSMServer::sessionList() { QStringList sessions( QStringLiteral( "default" ) ); KSharedConfig::Ptr config = KSharedConfig::openConfig(); const QStringList groups = config->groupList(); for ( QStringList::ConstIterator it = groups.constBegin(); it != groups.constEnd(); ++it ) if ( (*it).startsWith( QLatin1String( "Session: " ) ) ) sessions << (*it).mid( 9 ); return sessions; } bool KSMServer::isWM( const KSMClient* client ) const { return isWM( client->program()); } bool KSMServer::isWM( const QString& program ) const { // Strip possible paths, so that even /usr/bin/kwin is recognized as kwin. QString wmName = wm.mid( wm.lastIndexOf( QDir::separator()) + 1 ); QString programName = program.mid( program.lastIndexOf( QDir::separator()) + 1 ); return programName == wmName; } bool KSMServer::defaultSession() const { return sessionGroup.isEmpty(); } void KSMServer::setupShortcuts() { if (KAuthorized::authorize( QStringLiteral( "logout" ))) { KActionCollection* actionCollection = new KActionCollection(this); QAction* a; a = actionCollection->addAction(QStringLiteral("Log Out")); a->setText(i18n("Log Out")); KGlobalAccel::self()->setShortcut(a, QList() << Qt::ALT+Qt::CTRL+Qt::Key_Delete); connect(a, &QAction::triggered, this, &KSMServer::defaultLogout); a = actionCollection->addAction(QStringLiteral("Log Out Without Confirmation")); a->setText(i18n("Log Out Without Confirmation")); KGlobalAccel::self()->setShortcut(a, QList() << Qt::ALT+Qt::CTRL+Qt::SHIFT+Qt::Key_Delete); connect(a, &QAction::triggered, this, &KSMServer::logoutWithoutConfirmation); a = actionCollection->addAction(QStringLiteral("Halt Without Confirmation")); a->setText(i18n("Halt Without Confirmation")); KGlobalAccel::self()->setShortcut(a, QList() << Qt::ALT+Qt::CTRL+Qt::SHIFT+Qt::Key_PageDown); connect(a, &QAction::triggered, this, &KSMServer::haltWithoutConfirmation); a = actionCollection->addAction(QStringLiteral("Reboot Without Confirmation")); a->setText(i18n("Reboot Without Confirmation")); KGlobalAccel::self()->setShortcut(a, QList() << Qt::ALT+Qt::CTRL+Qt::SHIFT+Qt::Key_PageUp); connect(a, &QAction::triggered, this, &KSMServer::rebootWithoutConfirmation); } } /*! Restores the previous session. Ensures the window manager is running (if specified). */ void KSMServer::restoreSession( const QString &sessionName ) { if( state != Idle ) return; #ifdef KSMSERVER_STARTUP_DEBUG1 t.start(); #endif state = LaunchingWM; qCDebug(KSMSERVER) << "KSMServer::restoreSession " << sessionName; KSharedConfig::Ptr config = KSharedConfig::openConfig(); sessionGroup = QStringLiteral("Session: ") + sessionName; KConfigGroup configSessionGroup( config, sessionGroup); int count = configSessionGroup.readEntry( "count", 0 ); appsToStart = count; // find all commands to launch the wm in the session QList wmStartCommands; if ( !wm.isEmpty() ) { for ( int i = 1; i <= count; i++ ) { QString n = QString::number(i); if ( isWM( configSessionGroup.readEntry( QStringLiteral("program")+n, QString())) ) { wmStartCommands << configSessionGroup.readEntry( QStringLiteral("restartCommand")+n, QStringList() ); } } } if( wmStartCommands.isEmpty()) // otherwise use the configured default wmStartCommands << wmCommands; launchWM( wmStartCommands ); } void KSMServer::launchWM( const QList< QStringList >& wmStartCommands ) { assert( state == LaunchingWM ); if (!(qEnvironmentVariableIsSet("WAYLAND_DISPLAY") || qEnvironmentVariableIsSet("WAYLAND_SOCKET"))) { // when we have a window manager, we start it first and give // it some time before launching other processes. Results in a // visually more appealing startup. wmProcess = startApplication( wmStartCommands[ 0 ], QString(), QString(), true ); connect( wmProcess, SIGNAL(error(QProcess::ProcessError)), SLOT(wmProcessChange())); connect( wmProcess, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(wmProcessChange())); } emit windowManagerLoaded(); } void KSMServer::wmProcessChange() { if( state != LaunchingWM ) { // don't care about the process when not in the wm-launching state anymore wmProcess = nullptr; return; } if( wmProcess->state() == QProcess::NotRunning ) { // wm failed to launch for some reason, go with kwin instead qCWarning(KSMSERVER) << "Window manager" << wm << "failed to launch"; if( wm == QStringLiteral( KWIN_BIN ) ) return; // uhoh, kwin itself failed qCDebug(KSMSERVER) << "Launching KWin"; wm = QStringLiteral( KWIN_BIN ); wmCommands = ( QStringList() << QStringLiteral( KWIN_BIN ) ); // launch it launchWM( QList< QStringList >() << wmCommands ); return; } } /*! Starts the default session. Currently, that's the window manager only (if specified). */ void KSMServer::startDefaultSession() { if( state != Idle ) return; state = LaunchingWM; #ifdef KSMSERVER_STARTUP_DEBUG1 t.start(); #endif sessionGroup = QString(); launchWM( QList< QStringList >() << wmCommands ); } void KSMServer::restoreSubSession( const QString& name ) { sessionGroup = QStringLiteral( "SubSession: " ) + name; KConfigGroup configSessionGroup( KSharedConfig::openConfig(), sessionGroup); int count = configSessionGroup.readEntry( "count", 0 ); appsToStart = count; lastAppStarted = 0; lastIdStarted.clear(); state = RestoringSubSession; tryRestoreNext(); } void KSMServer::clientSetProgram( KSMClient* client ) { if( client->program() == wm ) { emit windowManagerLoaded(); } } void KSMServer::clientRegistered( const char* previousId ) { if ( previousId && lastIdStarted == QString::fromLocal8Bit( previousId ) ) tryRestoreNext(); } void KSMServer::tryRestoreNext() { if( state != Restoring && state != RestoringSubSession ) return; restoreTimer.stop(); KConfigGroup config(KSharedConfig::openConfig(), sessionGroup ); while ( lastAppStarted < appsToStart ) { lastAppStarted++; QString n = QString::number(lastAppStarted); QString clientId = config.readEntry( QStringLiteral("clientId")+n, QString() ); bool alreadyStarted = false; foreach ( KSMClient *c, clients ) { if ( QString::fromLocal8Bit( c->clientId() ) == clientId ) { alreadyStarted = true; break; } } if ( alreadyStarted ) continue; QStringList restartCommand = config.readEntry( QStringLiteral("restartCommand")+n, QStringList() ); if ( restartCommand.isEmpty() || (config.readEntry( QStringLiteral("restartStyleHint")+n, 0 ) == SmRestartNever)) { continue; } if ( isWM( config.readEntry( QStringLiteral("program")+n, QString())) ) continue; // wm already started if( config.readEntry( QStringLiteral( "wasWm" )+n, false )) continue; // it was wm before, but not now, don't run it (some have --replace in command :( ) startApplication( restartCommand, config.readEntry( QStringLiteral("clientMachine")+n, QString() ), config.readEntry( QStringLiteral("userId")+n, QString() )); lastIdStarted = clientId; if ( !lastIdStarted.isEmpty() ) { restoreTimer.setSingleShot( true ); restoreTimer.start( 2000 ); return; // we get called again from the clientRegistered handler } } //all done appsToStart = 0; lastIdStarted.clear(); if (state == Restoring) { emit sessionRestored(); } else { //subsession emit subSessionOpened(); } state = Idle; } void KSMServer::startupDone() { state = Idle; } void KSMServer::defaultLogout() { shutdown(KWorkSpace::ShutdownConfirmYes, KWorkSpace::ShutdownTypeDefault, KWorkSpace::ShutdownModeDefault); } void KSMServer::logoutWithoutConfirmation() { shutdown(KWorkSpace::ShutdownConfirmNo, KWorkSpace::ShutdownTypeNone, KWorkSpace::ShutdownModeDefault); } void KSMServer::haltWithoutConfirmation() { shutdown(KWorkSpace::ShutdownConfirmNo, KWorkSpace::ShutdownTypeHalt, KWorkSpace::ShutdownModeDefault); } void KSMServer::rebootWithoutConfirmation() { shutdown(KWorkSpace::ShutdownConfirmNo, KWorkSpace::ShutdownTypeReboot, KWorkSpace::ShutdownModeDefault); } void KSMServer::openSwitchUserDialog() { //this method exists only for compatibility. Users should ideally call this directly OrgKdeScreensaverInterface iface(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QDBusConnection::sessionBus()); iface.SwitchUser(); } diff --git a/libnotificationmanager/CMakeLists.txt b/libnotificationmanager/CMakeLists.txt index afbb801e8..1139cacf5 100644 --- a/libnotificationmanager/CMakeLists.txt +++ b/libnotificationmanager/CMakeLists.txt @@ -1,116 +1,121 @@ add_definitions(-DTRANSLATION_DOMAIN=\"libnotificationmanager\") add_subdirectory(declarative) if(BUILD_TESTING) add_subdirectory(autotests) endif() set(notificationmanager_LIB_SRCS server.cpp server_p.cpp settings.cpp notifications.cpp notification.cpp notificationsmodel.cpp notificationfilterproxymodel.cpp notificationsortproxymodel.cpp notificationgroupingproxymodel.cpp notificationgroupcollapsingproxymodel.cpp jobsmodel.cpp jobsmodel_p.cpp job.cpp job_p.cpp limitedrowcountproxymodel.cpp utils.cpp ) ecm_qt_declare_logging_category(notificationmanager_LIB_SRCS HEADER debug.h IDENTIFIER NOTIFICATIONMANAGER CATEGORY_NAME org.kde.plasma.notifications) -install(FILES libnotificationmanager.categories DESTINATION ${KDE_INSTALL_CONFDIR}) + +if (${ECM_VERSION} STRGREATER "5.58.0") + install(FILES libnotificationmanager.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) +else() + install(FILES libnotificationmanager.categories DESTINATION ${KDE_INSTALL_CONFDIR}) +endif() # Settings kconfig_add_kcfg_files(notificationmanager_LIB_SRCS kcfg/donotdisturbsettings.kcfgc) kconfig_add_kcfg_files(notificationmanager_LIB_SRCS kcfg/notificationsettings.kcfgc) kconfig_add_kcfg_files(notificationmanager_LIB_SRCS kcfg/jobsettings.kcfgc) kconfig_add_kcfg_files(notificationmanager_LIB_SRCS kcfg/badgesettings.kcfgc) # DBus # Notifications qt5_add_dbus_adaptor(notificationmanager_LIB_SRCS dbus/org.freedesktop.Notifications.xml server_p.h NotificationManager::ServerPrivate) # JobView qt5_add_dbus_adaptor(notificationmanager_LIB_SRCS dbus/org.kde.kuiserver.xml jobsmodel_p.h NotificationManager::JobsModelPrivate) qt5_add_dbus_adaptor(notificationmanager_LIB_SRCS dbus/org.kde.JobViewServer.xml jobsmodel_p.h NotificationManager::JobsModelPrivate) qt5_add_dbus_adaptor(notificationmanager_LIB_SRCS dbus/org.kde.JobViewServerV2.xml jobsmodel_p.h NotificationManager::JobsModelPrivate) qt5_add_dbus_adaptor(notificationmanager_LIB_SRCS dbus/org.kde.JobViewV2.xml job_p.h NotificationManager::JobPrivate) qt5_add_dbus_adaptor(notificationmanager_LIB_SRCS dbus/org.kde.JobViewV3.xml job_p.h NotificationManager::JobPrivate) add_library(notificationmanager ${notificationmanager_LIB_SRCS}) add_library(PW::LibNotificationManager ALIAS notificationmanager) target_compile_definitions(notificationmanager PRIVATE -DPROJECT_VERSION="${PROJECT_VERSION}") generate_export_header(notificationmanager) target_include_directories(notificationmanager PUBLIC "$" "$") target_link_libraries(notificationmanager PUBLIC Qt5::Core Qt5::Gui Qt5::Quick KF5::ConfigCore KF5::ItemModels PRIVATE Qt5::DBus KF5::ConfigGui KF5::I18n KF5::IconThemes KF5::KIOFileWidgets KF5::Plasma KF5::ProcessCore KF5::Service ) set_target_properties(notificationmanager PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION 1 EXPORT_NAME LibNotificationManager) install(TARGETS notificationmanager EXPORT notificationmanagerLibraryTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ) install(FILES server.h notifications.h notification.h jobsmodel.h job.h settings.h ${CMAKE_CURRENT_BINARY_DIR}/notificationmanager_export.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/notificationmanager COMPONENT Devel ) write_basic_config_version_file(${CMAKE_CURRENT_BINARY_DIR}/LibNotificationManagerConfigVersion.cmake VERSION "${PROJECT_VERSION}" COMPATIBILITY AnyNewerVersion) set(CMAKECONFIG_INSTALL_DIR ${KDE_INSTALL_LIBDIR}/cmake/LibNotificationManager) configure_package_config_file(LibNotificationManagerConfig.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/LibNotificationManagerConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/LibNotificationManagerConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/LibNotificationManagerConfigVersion.cmake DESTINATION ${CMAKECONFIG_INSTALL_DIR}) install(EXPORT notificationmanagerLibraryTargets NAMESPACE PW:: DESTINATION ${CMAKECONFIG_INSTALL_DIR} FILE LibNotificationManagerLibraryTargets.cmake ) install(FILES plasmanotifyrc DESTINATION ${KDE_INSTALL_CONFDIR}) diff --git a/libnotificationmanager/notification.cpp b/libnotificationmanager/notification.cpp index 9e55e48e8..8847670a5 100644 --- a/libnotificationmanager/notification.cpp +++ b/libnotificationmanager/notification.cpp @@ -1,654 +1,676 @@ /* * Copyright 2008 Dmitry Suzdalev * Copyright 2017 David Edmundson * Copyright 2018-2019 Kai Uwe Broulik * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "notification.h" #include "notification_p.h" #include "notifications.h" #include #include #include #include #include #include #include #include #include #include #include +#include #include "debug.h" #include "notifications.h" using namespace NotificationManager; Notification::Private::Private() { } Notification::Private::~Private() = default; QString Notification::Private::sanitize(const QString &text) { // replace all \ns with
QString t = text; t.replace(QLatin1String("\n"), QStringLiteral("
")); // Now remove all inner whitespace (\ns are already
s) t = t.simplified(); // Finally, check if we don't have multiple
s following, // can happen for example when "\n \n" is sent, this replaces // all
s in succsession with just one t.replace(QRegularExpression(QStringLiteral("
\\s*
(\\s|
)*")), QLatin1String("
")); // This fancy RegExp escapes every occurrence of & since QtQuick Text will blatantly cut off // text where it finds a stray ampersand. // Only &{apos, quot, gt, lt, amp}; as well as { character references will be allowed t.replace(QRegularExpression(QStringLiteral("&(?!(?:apos|quot|[gl]t|amp);|#)")), QLatin1String("&")); // Don't bother adding some HTML structure if the body is now empty if (t.isEmpty()) { return t; } QXmlStreamReader r(QStringLiteral("") + t + QStringLiteral("")); QString result; QXmlStreamWriter out(&result); const QVector allowedTags = {"b", "i", "u", "img", "a", "html", "br", "table", "tr", "td"}; out.writeStartDocument(); while (!r.atEnd()) { r.readNext(); if (r.tokenType() == QXmlStreamReader::StartElement) { const QString name = r.name().toString(); if (!allowedTags.contains(name)) { continue; } out.writeStartElement(name); if (name == QLatin1String("img")) { auto src = r.attributes().value("src").toString(); auto alt = r.attributes().value("alt").toString(); const QUrl url(src); if (url.isLocalFile()) { out.writeAttribute(QStringLiteral("src"), src); } else { //image denied for security reasons! Do not copy the image src here! } out.writeAttribute(QStringLiteral("alt"), alt); } if (name == QLatin1String("a")) { out.writeAttribute(QStringLiteral("href"), r.attributes().value("href").toString()); } } if (r.tokenType() == QXmlStreamReader::EndElement) { const QString name = r.name().toString(); if (!allowedTags.contains(name)) { continue; } out.writeEndElement(); } if (r.tokenType() == QXmlStreamReader::Characters) { const auto text = r.text().toString(); out.writeCharacters(text); //this auto escapes chars -> HTML entities } } out.writeEndDocument(); if (r.hasError()) { qCWarning(NOTIFICATIONMANAGER) << "Notification to send to backend contains invalid XML: " << r.errorString() << "line" << r.lineNumber() << "col" << r.columnNumber(); } // The Text.StyledText format handles only html3.2 stuff and ' is html4 stuff // so we need to replace it here otherwise it will not render at all. result = result.replace(QLatin1String("'"), QChar('\'')); return result; } QImage Notification::Private::decodeNotificationSpecImageHint(const QDBusArgument &arg) { int width, height, rowStride, hasAlpha, bitsPerSample, channels; QByteArray pixels; char* ptr; char* end; arg.beginStructure(); arg >> width >> height >> rowStride >> hasAlpha >> bitsPerSample >> channels >> pixels; arg.endStructure(); #define SANITY_CHECK(condition) \ if (!(condition)) { \ qCWarning(NOTIFICATIONMANAGER) << "Image decoding sanity check failed on" << #condition; \ return QImage(); \ } SANITY_CHECK(width > 0); SANITY_CHECK(width < 2048); SANITY_CHECK(height > 0); SANITY_CHECK(height < 2048); SANITY_CHECK(rowStride > 0); #undef SANITY_CHECK auto copyLineRGB32 = [](QRgb* dst, const char* src, int width) { const char* end = src + width * 3; for (; src != end; ++dst, src+=3) { *dst = qRgb(src[0], src[1], src[2]); } }; auto copyLineARGB32 = [](QRgb* dst, const char* src, int width) { const char* end = src + width * 4; for (; src != end; ++dst, src+=4) { *dst = qRgba(src[0], src[1], src[2], src[3]); } }; QImage::Format format = QImage::Format_Invalid; void (*fcn)(QRgb*, const char*, int) = nullptr; if (bitsPerSample == 8) { if (channels == 4) { format = QImage::Format_ARGB32; fcn = copyLineARGB32; } else if (channels == 3) { format = QImage::Format_RGB32; fcn = copyLineRGB32; } } if (format == QImage::Format_Invalid) { qCWarning(NOTIFICATIONMANAGER) << "Unsupported image format (hasAlpha:" << hasAlpha << "bitsPerSample:" << bitsPerSample << "channels:" << channels << ")"; return QImage(); } QImage image(width, height, format); ptr = pixels.data(); end = ptr + pixels.length(); for (int y=0; y end) { qCWarning(NOTIFICATIONMANAGER) << "Image data is incomplete. y:" << y << "height:" << height; break; } fcn((QRgb*)image.scanLine(y), ptr, width); } return image; } void Notification::Private::sanitizeImage(QImage &image) { if (image.isNull()) { return; } const QSize max = maximumImageSize(); if (image.size().width() > max.width() || image.size().height() > max.height()) { image = image.scaled(max, Qt::KeepAspectRatio, Qt::SmoothTransformation); } } void Notification::Private::loadImagePath(const QString &path) { // image_path and appIcon should either be a URL with file scheme or the name of a themed icon. // We're lenient and also allow local paths. image = QImage(); // clear icon.clear(); QUrl imageUrl; if (path.startsWith(QLatin1Char('/'))) { imageUrl = QUrl::fromLocalFile(path); } else if (path.contains(QLatin1Char('/'))) { // bad heuristic to detect a URL imageUrl = QUrl(path); if (!imageUrl.isLocalFile()) { qCDebug(NOTIFICATIONMANAGER) << "Refused to load image from" << path << "which isn't a valid local location."; return; } } if (!imageUrl.isValid()) { // try icon path instead; icon = path; return; } QImageReader reader(imageUrl.toLocalFile()); reader.setAutoTransform(true); const QSize imageSize = reader.size(); if (imageSize.isValid() && (imageSize.width() > maximumImageSize().width() || imageSize.height() > maximumImageSize().height())) { const QSize thumbnailSize = imageSize.scaled(maximumImageSize(), Qt::KeepAspectRatio); reader.setScaledSize(thumbnailSize); } image = reader.read(); } QString Notification::Private::defaultComponentName() { // NOTE Keep in sync with KNotification return QStringLiteral("plasma_workspace"); } QSize Notification::Private::maximumImageSize() { return QSize(256, 256); } +KService::Ptr Notification::Private::serviceForDesktopEntry(const QString &desktopEntry) +{ + KService::Ptr service = KService::serviceByDesktopName(desktopEntry); + + if (!service) { + const QString lowerDesktopEntry = desktopEntry.toLower(); + service = KService::serviceByDesktopName(lowerDesktopEntry); + } + + // Try if it's a renamed flatpak + if (!service) { + const QString desktopId = desktopEntry + QLatin1String(".desktop"); + // HACK Querying for XDG lists in KServiceTypeTrader does not work, do it manually + const auto services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), + QStringLiteral("exist Exec and exist [X-Flatpak-RenamedFrom]")); + for (auto it = services.constBegin(); it != services.constEnd() && !service; ++it) { + const QVariant renamedFrom = (*it)->property(QStringLiteral("X-Flatpak-RenamedFrom"), QVariant::String); + const auto names = renamedFrom.toString().split(QChar(';')); + for (const QString &name : names) { + if (name == desktopId) { + service = *it; + break; + } + } + } + } + + return service; +} + void Notification::Private::processHints(const QVariantMap &hints) { auto end = hints.end(); desktopEntry = hints.value(QStringLiteral("desktop-entry")).toString(); QString serviceName; configurableService = false; - if (!desktopEntry.isEmpty()) { - KService::Ptr service = KService::serviceByDesktopName(desktopEntry); - // Also try lower-case desktopEntry (Firefox sends "Firefox" which doesn't match "firefox"...) - if (!service) { - const QString lowerDesktopEntry = desktopEntry.toLower(); - service = KService::serviceByDesktopName(lowerDesktopEntry); - if (service) { - qCInfo(NOTIFICATIONMANAGER) << "Application sent desktop-entry" << desktopEntry << "but it actually was" << lowerDesktopEntry << ", this is an application bug!"; - desktopEntry = lowerDesktopEntry; - } - } - if (service) { - serviceName = service->name(); - applicationIconName = service->icon(); - configurableService = !service->noDisplay(); - } + + KService::Ptr service = serviceForDesktopEntry(desktopEntry); + if (service) { + desktopEntry = service->desktopEntryName(); + serviceName = service->name(); + applicationIconName = service->icon(); + configurableService = !service->noDisplay(); } notifyRcName = hints.value(QStringLiteral("x-kde-appname")).toString(); const bool isDefaultEvent = (notifyRcName == defaultComponentName()); configurableNotifyRc = false; if (!notifyRcName.isEmpty()) { // Check whether the application actually has notifications we can configure KConfig config(notifyRcName + QStringLiteral(".notifyrc"), KConfig::NoGlobals); config.addConfigSources(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("knotifications5/") + notifyRcName + QStringLiteral(".notifyrc"))); KConfigGroup globalGroup(&config, "Global"); const QString iconName = globalGroup.readEntry("IconName"); // For default events we try to show the application name from the desktop entry if possible // This will have us show e.g. "Dr Konqi" instead of generic "Plasma Desktop" if (isDefaultEvent && !serviceName.isEmpty()) { applicationName = serviceName; } // also only overwrite application icon name for non-default events (or if we don't have a service icon) if (!iconName.isEmpty() && (!isDefaultEvent || applicationIconName.isEmpty())) { applicationIconName = iconName; } const QRegularExpression regexp(QStringLiteral("^Event/([^/]*)$")); configurableNotifyRc = !config.groupList().filter(regexp).isEmpty(); } // Special override for KDE Connect since the notification is sent by kdeconnectd // but actually comes from a different app on the phone const QString applicationDisplayName = hints.value(QStringLiteral("x-kde-display-appname")).toString(); if (!applicationDisplayName.isEmpty()) { applicationName = applicationDisplayName; } originName = hints.value(QStringLiteral("x-kde-origin-name")).toString(); eventId = hints.value(QStringLiteral("x-kde-eventId")).toString(); bool ok; const int urgency = hints.value(QStringLiteral("urgency")).toInt(&ok); // DBus type is actually "byte" if (ok) { // FIXME use separate enum again switch (urgency) { case 0: setUrgency(Notifications::LowUrgency); break; case 1: setUrgency(Notifications::NormalUrgency); break; case 2: setUrgency(Notifications::CriticalUrgency); break; } } urls = QUrl::fromStringList(hints.value(QStringLiteral("x-kde-urls")).toStringList()); // Underscored hints was in use in version 1.1 of the spec but has been // replaced by dashed hints in version 1.2. We need to support it for // users of the 1.2 version of the spec. auto it = hints.find(QStringLiteral("image-data")); if (it == end) { it = hints.find(QStringLiteral("image_data")); } if (it == end) { // This hint was in use in version 1.0 of the spec but has been // replaced by "image_data" in version 1.1. We need to support it for // users of the 1.0 version of the spec. it = hints.find(QStringLiteral("icon_data")); } if (it != end) { image = decodeNotificationSpecImageHint(it->value()); } if (image.isNull()) { it = hints.find(QStringLiteral("image-path")); if (it == end) { it = hints.find(QStringLiteral("image_path")); } if (it != end) { loadImagePath(it->toString()); } } sanitizeImage(image); } void Notification::Private::setUrgency(Notifications::Urgency urgency) { this->urgency = urgency; // Critical notifications must not time out // TODO should we really imply this here and not on the view side? // are there usecases for critical but can expire? // "critical updates available"? if (urgency == Notifications::CriticalUrgency) { timeout = 0; } } Notification::Notification(uint id) : d(new Private()) { d->id = id; d->created = QDateTime::currentDateTimeUtc(); } Notification::Notification(const Notification &other) : d(new Private(*other.d)) { } Notification::Notification(Notification &&other) : d(other.d) { other.d = nullptr; } Notification &Notification::operator=(const Notification &other) { d = new Private(*other.d); return *this; } Notification &Notification::operator=(Notification &&other) { d = other.d; other.d = nullptr; return *this; } Notification::~Notification() { delete d; } uint Notification::id() const { return d->id; } QDateTime Notification::created() const { return d->created; } QDateTime Notification::updated() const { return d->updated; } void Notification::resetUpdated() { d->updated = QDateTime::currentDateTimeUtc(); } QString Notification::summary() const { return d->summary; } void Notification::setSummary(const QString &summary) { d->summary = summary; } QString Notification::body() const { return d->body; } void Notification::setBody(const QString &body) { d->body = Private::sanitize(body.trimmed()); } QString Notification::icon() const { return d->icon; } void Notification::setIcon(const QString &icon) { d->loadImagePath(icon); Private::sanitizeImage(d->image); } QImage Notification::image() const { return d->image; } void Notification::setImage(const QImage &image) { d->image = image; } QString Notification::desktopEntry() const { return d->desktopEntry; } QString Notification::notifyRcName() const { return d->notifyRcName; } QString Notification::eventId() const { return d->eventId; } QString Notification::applicationName() const { return d->applicationName; } void Notification::setApplicationName(const QString &applicationName) { d->applicationName = applicationName; } QString Notification::applicationIconName() const { return d->applicationIconName; } void Notification::setApplicationIconName(const QString &applicationIconName) { d->applicationIconName = applicationIconName; } QString Notification::originName() const { return d->originName; } QStringList Notification::actionNames() const { return d->actionNames; } QStringList Notification::actionLabels() const { return d->actionLabels; } bool Notification::hasDefaultAction() const { return d->hasDefaultAction; } QString Notification::defaultActionLabel() const { return d->defaultActionLabel; } void Notification::setActions(const QStringList &actions) { if (actions.count() % 2 != 0) { qCWarning(NOTIFICATIONMANAGER) << "List of actions must contain an even number of items, tried to set actions to" << actions; return; } d->hasDefaultAction = false; d->hasConfigureAction = false; QStringList names; QStringList labels; for (int i = 0; i < actions.count(); i += 2) { const QString &name = actions.at(i); const QString &label = actions.at(i + 1); if (!d->hasDefaultAction && name == QLatin1String("default")) { d->hasDefaultAction = true; d->defaultActionLabel = label; continue; } if (!d->hasConfigureAction && name == QLatin1String("settings")) { d->hasConfigureAction = true; d->configureActionLabel = label; continue; } names << name; labels << label; } d->actionNames = names; d->actionLabels = labels; } QList Notification::urls() const { return d->urls; } void Notification::setUrls(const QList &urls) { d->urls = urls; } Notifications::Urgency Notification::urgency() const { return d->urgency; } int Notification::timeout() const { return d->timeout; } void Notification::setTimeout(int timeout) { d->timeout = timeout; } bool Notification::configurable() const { return d->hasConfigureAction || d->configurableNotifyRc || d->configurableService; } QString Notification::configureActionLabel() const { return d->configureActionLabel; } bool Notification::expired() const { return d->expired; } void Notification::setExpired(bool expired) { d->expired = expired; } bool Notification::dismissed() const { return d->dismissed; } void Notification::setDismissed(bool dismissed) { d->dismissed = dismissed; } void Notification::processHints(const QVariantMap &hints) { d->processHints(hints); } diff --git a/libnotificationmanager/notification_p.h b/libnotificationmanager/notification_p.h index c3e2bc8d1..979d357cc 100644 --- a/libnotificationmanager/notification_p.h +++ b/libnotificationmanager/notification_p.h @@ -1,93 +1,97 @@ /* * Copyright 2018-2019 Kai Uwe Broulik * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #pragma once #include #include #include #include #include #include #include +#include + #include "notifications.h" namespace NotificationManager { class Q_DECL_HIDDEN Notification::Private { public: Private(); ~Private(); static QString sanitize(const QString &text); static QImage decodeNotificationSpecImageHint(const QDBusArgument &arg); static void sanitizeImage(QImage &image); void loadImagePath(const QString &path); static QString defaultComponentName(); static QSize maximumImageSize(); + static KService::Ptr serviceForDesktopEntry(const QString &desktopEntry); + void processHints(const QVariantMap &hints); void setUrgency(Notifications::Urgency urgency); uint id = 0; QDateTime created; QDateTime updated; QString summary; QString body; // Can be theme icon name or path QString icon; QImage image; QString applicationName; QString desktopEntry; bool configurableService = false; QString applicationIconName; QString originName; QStringList actionNames; QStringList actionLabels; bool hasDefaultAction = false; QString defaultActionLabel; bool hasConfigureAction = false; QString configureActionLabel; bool configurableNotifyRc = false; QString notifyRcName; QString eventId; QList urls; Notifications::Urgency urgency = Notifications::NormalUrgency; int timeout = -1; bool expired = false; bool dismissed = false; }; } // namespace NotificationManager diff --git a/plasma.desktop.cmake b/plasma.desktop.cmake index 19cfddafa..4d361606d 100644 --- a/plasma.desktop.cmake +++ b/plasma.desktop.cmake @@ -1,96 +1,96 @@ [Desktop Entry] Type=XSession -Exec=${CMAKE_INSTALL_FULL_BINDIR}/startkde -TryExec=${CMAKE_INSTALL_FULL_BINDIR}/startkde +Exec=${CMAKE_INSTALL_FULL_BINDIR}/startplasma-x11 +TryExec=${CMAKE_INSTALL_FULL_BINDIR}/startplasma-x11 DesktopNames=KDE Name=Plasma Name[ar]=بلازما Name[bs]=Plazma Name[ca]=Plasma Name[ca@valencia]=Plasma Name[cs]=Plasma Name[da]=Plasma Name[de]=Plasma Name[el]=Plasma Name[en_GB]=Plasma Name[es]=Plasma Name[et]=Plasma Name[eu]=Plasma Name[fi]=Plasma Name[fr]=Plasma Name[gl]=Plasma Name[he]=פלזמה Name[hu]=Plasma Name[id]=Plasma Name[is]=Plasma Name[it]=Plasma Name[ja]=プラズマ Name[ko]=Plasma Name[lt]=Plasma Name[nb]=Plasma Name[nds]=Plasma Name[nl]=Plasma Name[nn]=Plasma Name[pa]=ਪਲਾਜ਼ਮਾ Name[pl]=Plazma Name[pt]=Plasma Name[pt_BR]=Plasma Name[ru]=Plasma Name[sk]=Plasma Name[sl]=Plasma Name[sr]=Плазма Name[sr@ijekavian]=Плазма Name[sr@ijekavianlatin]=Plasma Name[sr@latin]=Plasma Name[sv]=Plasma Name[tr]=Plama Name[uk]=Плазма Name[x-test]=xxPlasmaxx Name[zh_CN]=Plasma Name[zh_TW]=Plasma Comment=Plasma by KDE Comment[ar]=بلازما كدي Comment[bs]=Plazma od strane KDe Comment[ca]=Plasma, creat per la comunitat KDE Comment[ca@valencia]=Plasma, creat per la comunitat KDE Comment[cs]=Plasma z KDE Comment[da]=Plasma fra KDE Comment[de]=Plasma von KDE Comment[el]=Plasma από το KDE Comment[en_GB]=Plasma by KDE Comment[es]=Plasma, por KDE Comment[et]=KDE Plasma Comment[eu]=KDEren Plasma Comment[fi]=Plasma KDE:ltä Comment[fr]=Plasma, par KDE Comment[gl]=Plasma, fornecido por KDE. Comment[he]=פלזמה באמצעות KDE Comment[hu]=Plasma a KDE-től Comment[id]=Plasma oleh KDE Comment[is]=Plasma frá KDE Comment[it]=Plasma di KDE Comment[ja]=Plasma by KDE Comment[ko]=KDE Plasma Comment[lt]=Plasmą sukūrė KDE Comment[nb]=Plasma av KDE Comment[nds]=Plasma vun KDE Comment[nl]=Plasma door KDE Comment[nn]=Plasma frå KDE Comment[pa]=KDE ਵਲੋਂ ਪਲਾਜ਼ਮਾ Comment[pl]=Plazma dzięki KDE Comment[pt]=Plasma do KDE Comment[pt_BR]=Plasma do KDE Comment[ru]=KDE Plasma Comment[sk]=Plasma od KDE Comment[sl]=KDE Plasma Comment[sr]=Плазма од КДЕ‑а Comment[sr@ijekavian]=Плазма од КДЕ‑а Comment[sr@ijekavianlatin]=Plasma od KDE‑a Comment[sr@latin]=Plasma od KDE‑a Comment[sv]=Plasma av KDE Comment[tr]=KDE Plasma Comment[uk]=Плазма KDE Comment[x-test]=xxPlasma by KDExx Comment[zh_CN]=KDE Plasma Comment[zh_TW]=Plasma by KDE X-KDE-PluginInfo-Version=${PROJECT_VERSION} diff --git a/plasmawayland.desktop.cmake b/plasmawayland.desktop.cmake index 9e16f4392..2415ec285 100644 --- a/plasmawayland.desktop.cmake +++ b/plasmawayland.desktop.cmake @@ -1,95 +1,95 @@ [Desktop Entry] -Exec=dbus-run-session ${CMAKE_INSTALL_FULL_BINDIR}/startplasmacompositor -TryExec=${CMAKE_INSTALL_FULL_BINDIR}/startplasmacompositor +Exec=dbus-run-session ${CMAKE_INSTALL_FULL_BINDIR}/startplasma-wayland +TryExec=${CMAKE_INSTALL_FULL_BINDIR}/startplasma-wayland DesktopNames=KDE Name=Plasma Name[ar]=بلازما Name[bs]=Plazma Name[ca]=Plasma Name[ca@valencia]=Plasma Name[cs]=Plasma Name[da]=Plasma Name[de]=Plasma Name[el]=Plasma Name[en_GB]=Plasma Name[es]=Plasma Name[et]=Plasma Name[eu]=Plasma Name[fi]=Plasma Name[fr]=Plasma Name[gl]=Plasma Name[he]=פלזמה Name[hu]=Plasma Name[id]=Plasma Name[is]=Plasma Name[it]=Plasma Name[ja]=プラズマ Name[ko]=Plasma Name[lt]=Plasma Name[nb]=Plasma Name[nds]=Plasma Name[nl]=Plasma Name[nn]=Plasma Name[pa]=ਪਲਾਜ਼ਮਾ Name[pl]=Plazma Name[pt]=Plasma Name[pt_BR]=Plasma Name[ru]=Plasma Name[sk]=Plasma Name[sl]=Plasma Name[sr]=Плазма Name[sr@ijekavian]=Плазма Name[sr@ijekavianlatin]=Plasma Name[sr@latin]=Plasma Name[sv]=Plasma Name[tr]=Plama Name[uk]=Плазма Name[x-test]=xxPlasmaxx Name[zh_CN]=Plasma Name[zh_TW]=Plasma Comment=Plasma by KDE Comment[ar]=بلازما كدي Comment[bs]=Plazma od strane KDe Comment[ca]=Plasma, creat per la comunitat KDE Comment[ca@valencia]=Plasma, creat per la comunitat KDE Comment[cs]=Plasma z KDE Comment[da]=Plasma fra KDE Comment[de]=Plasma von KDE Comment[el]=Plasma από το KDE Comment[en_GB]=Plasma by KDE Comment[es]=Plasma, por KDE Comment[et]=KDE Plasma Comment[eu]=KDEren Plasma Comment[fi]=Plasma KDE:ltä Comment[fr]=Plasma, par KDE Comment[gl]=Plasma, fornecido por KDE. Comment[he]=פלזמה באמצעות KDE Comment[hu]=Plasma a KDE-től Comment[id]=Plasma oleh KDE Comment[is]=Plasma frá KDE Comment[it]=Plasma di KDE Comment[ja]=Plasma by KDE Comment[ko]=KDE Plasma Comment[lt]=Plasmą sukūrė KDE Comment[nb]=Plasma av KDE Comment[nds]=Plasma vun KDE Comment[nl]=Plasma door KDE Comment[nn]=Plasma frå KDE Comment[pa]=KDE ਵਲੋਂ ਪਲਾਜ਼ਮਾ Comment[pl]=Plazma dzięki KDE Comment[pt]=Plasma do KDE Comment[pt_BR]=Plasma do KDE Comment[ru]=KDE Plasma Comment[sk]=Plasma od KDE Comment[sl]=KDE Plasma Comment[sr]=Плазма од КДЕ‑а Comment[sr@ijekavian]=Плазма од КДЕ‑а Comment[sr@ijekavianlatin]=Plasma od KDE‑a Comment[sr@latin]=Plasma od KDE‑a Comment[sv]=Plasma av KDE Comment[tr]=KDE Plasma Comment[uk]=Плазма KDE Comment[x-test]=xxPlasma by KDExx Comment[zh_CN]=KDE Plasma Comment[zh_TW]=Plasma by KDE X-KDE-PluginInfo-Version=${PROJECT_VERSION} diff --git a/sddm-theme/BreezeMenuStyle.qml b/sddm-theme/BreezeMenuStyle.qml new file mode 100644 index 000000000..7d56c1499 --- /dev/null +++ b/sddm-theme/BreezeMenuStyle.qml @@ -0,0 +1,25 @@ +import QtQuick 2.2 + +import org.kde.plasma.core 2.0 as PlasmaCore + +import QtQuick.Controls.Styles 1.4 as QQCS +import QtQuick.Controls 1.3 as QQC + +QQCS.MenuStyle { + frame: Rectangle { + color: PlasmaCore.ColorScope.backgroundColor + border.color: Qt.tint(PlasmaCore.ColorScope.textColor, Qt.rgba(color.r, color.g, color.b, 0.7)) + border.width: 1 + } + itemDelegate.label: QQC.Label { + height: contentHeight * 1.2 + verticalAlignment: Text.AlignVCenter + color: styleData.selected ? PlasmaCore.ColorScope.highlightedTextColor : PlasmaCore.ColorScope.textColor + font.pointSize: config.fontSize + text: styleData.text + } + itemDelegate.background: Rectangle { + visible: styleData.selected + color: PlasmaCore.ColorScope.highlightColor + } +} diff --git a/sddm-theme/KeyboardButton.qml b/sddm-theme/KeyboardButton.qml index 13d66c2c4..9989d93ef 100644 --- a/sddm-theme/KeyboardButton.qml +++ b/sddm-theme/KeyboardButton.qml @@ -1,37 +1,38 @@ import QtQuick 2.2 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import QtQuick.Controls 1.3 as QQC PlasmaComponents.ToolButton { id: keyboardButton property int currentIndex: -1 text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Keyboard Layout: %1", instantiator.objectAt(currentIndex).shortName) implicitWidth: minimumWidth font.pointSize: config.fontSize visible: menu.items.length > 1 Component.onCompleted: currentIndex = Qt.binding(function() {return keyboard.currentLayout}); menu: QQC.Menu { id: keyboardMenu + style: BreezeMenuStyle {} Instantiator { id: instantiator model: keyboard.layouts onObjectAdded: keyboardMenu.insertItem(index, object) onObjectRemoved: keyboardMenu.removeItem( object ) delegate: QQC.MenuItem { text: modelData.longName property string shortName: modelData.shortName onTriggered: { keyboard.currentLayout = model.index } } } } } diff --git a/sddm-theme/SessionButton.qml b/sddm-theme/SessionButton.qml index 5965af6e0..58590c835 100644 --- a/sddm-theme/SessionButton.qml +++ b/sddm-theme/SessionButton.qml @@ -1,58 +1,59 @@ /* * Copyright 2016 David Edmundson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 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 Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.2 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import QtQuick.Controls 1.3 as QQC PlasmaComponents.ToolButton { id: root property int currentIndex: -1 implicitWidth: minimumWidth visible: menu.items.length > 1 text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Desktop Session: %1", instantiator.objectAt(currentIndex).text || "") font.pointSize: config.fontSize Component.onCompleted: { currentIndex = sessionModel.lastIndex } menu: QQC.Menu { id: menu + style: BreezeMenuStyle {} Instantiator { id: instantiator model: sessionModel onObjectAdded: menu.insertItem(index, object) onObjectRemoved: menu.removeItem( object ) delegate: QQC.MenuItem { text: model.name onTriggered: { root.currentIndex = model.index } } } } } diff --git a/shell/panelview.cpp b/shell/panelview.cpp index bd87956cf..f0e98676b 100644 --- a/shell/panelview.cpp +++ b/shell/panelview.cpp @@ -1,1299 +1,1298 @@ /* * Copyright 2013 Marco Martin * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "panelview.h" #include "shellcorona.h" #include "panelshadows_p.h" #include "panelconfigview.h" #include "screenpool.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_X11 #include #include #include #include #endif static const int MINSIZE = 10; PanelView::PanelView(ShellCorona *corona, QScreen *targetScreen, QWindow *parent) : PlasmaQuick::ContainmentView(corona, parent), m_offset(0), m_maxLength(0), m_minLength(0), m_contentLength(0), m_distance(0), m_thickness(30), m_initCompleted(false), m_alignment(Qt::AlignLeft), m_corona(corona), m_visibilityMode(NormalPanel), m_backgroundHints(Plasma::Types::StandardBackground), m_shellSurface(nullptr) { if (targetScreen) { setPosition(targetScreen->geometry().center()); setScreenToFollow(targetScreen); setScreen(targetScreen); } setResizeMode(QuickViewSharedEngine::SizeRootObjectToView); setClearBeforeRendering(true); setColor(QColor(Qt::transparent)); setFlags(Qt::FramelessWindowHint|Qt::WindowDoesNotAcceptFocus); - connect(&m_theme, &Plasma::Theme::themeChanged, this, &PanelView::themeChanged); - connect(this, SIGNAL(backgroundHintsChanged()), this, SLOT(themeChanged())); + connect(&m_theme, &Plasma::Theme::themeChanged, this, &PanelView::updateMask); + connect(this, &PanelView::backgroundHintsChanged, this, &PanelView::updateMask); // TODO: add finished/componentComplete signal to QuickViewSharedEngine, // so we exactly know when rootobject is available connect(this, &QuickViewSharedEngine::statusChanged, this, &PanelView::handleQmlStatusChange); m_positionPaneltimer.setSingleShot(true); m_positionPaneltimer.setInterval(150); connect(&m_positionPaneltimer, &QTimer::timeout, this, &PanelView::restore); m_unhideTimer.setSingleShot(true); m_unhideTimer.setInterval(500); connect(&m_unhideTimer, &QTimer::timeout, this, &PanelView::restoreAutoHide); m_lastScreen = targetScreen; connect(this, SIGNAL(locationChanged(Plasma::Types::Location)), &m_positionPaneltimer, SLOT(start())); connect(this, SIGNAL(containmentChanged()), this, SLOT(containmentChanged())); if (!m_corona->kPackage().isValid()) { qWarning() << "Invalid home screen package"; } m_strutsTimer.setSingleShot(true); connect(&m_strutsTimer, &QTimer::timeout, this, &PanelView::updateStruts); qmlRegisterType(); rootContext()->setContextProperty(QStringLiteral("panel"), this); setSource(m_corona->kPackage().fileUrl("views", QStringLiteral("Panel.qml"))); } PanelView::~PanelView() { if (containment()) { m_corona->requestApplicationConfigSync(); } } KConfigGroup PanelView::panelConfig(ShellCorona *corona, Plasma::Containment *containment, QScreen *screen) { if (!containment || !screen) { return KConfigGroup(); } KConfigGroup views(corona->applicationConfig(), "PlasmaViews"); views = KConfigGroup(&views, QStringLiteral("Panel %1").arg(containment->id())); if (containment->formFactor() == Plasma::Types::Vertical) { return KConfigGroup(&views, QStringLiteral("Vertical") + QString::number(screen->size().height())); //treat everything else as horizontal } else { return KConfigGroup(&views, QStringLiteral("Horizontal") + QString::number(screen->size().width())); } } KConfigGroup PanelView::config() const { return panelConfig(m_corona, containment(), m_screenToFollow); } void PanelView::maximize() { int length; if (containment()->formFactor() == Plasma::Types::Vertical) { length = m_screenToFollow->size().height(); } else { length = m_screenToFollow->size().width(); } setOffset(0); setMinimumLength(length); setMaximumLength(length); } Qt::Alignment PanelView::alignment() const { return m_alignment; } void PanelView::setAlignment(Qt::Alignment alignment) { if (m_alignment == alignment) { return; } m_alignment = alignment; //alignment is not resolution dependent config().parent().writeEntry("alignment", (int)m_alignment); emit alignmentChanged(); positionPanel(); } int PanelView::offset() const { return m_offset; } void PanelView::setOffset(int offset) { if (m_offset == offset) { return; } if (formFactor() == Plasma::Types::Vertical) { if (offset + m_maxLength > m_screenToFollow->size().height()) { setMaximumLength( -m_offset + m_screenToFollow->size().height() ); } } else { if (offset + m_maxLength > m_screenToFollow->size().width()) { setMaximumLength( -m_offset + m_screenToFollow->size().width() ); } } m_offset = offset; config().writeEntry("offset", m_offset); positionPanel(); emit offsetChanged(); m_corona->requestApplicationConfigSync(); emit m_corona->availableScreenRegionChanged(); } int PanelView::thickness() const { return m_thickness; } void PanelView::setThickness(int value) { if (value == thickness()) { return; } m_thickness = value; emit thicknessChanged(); config().writeEntry("thickness", value); m_corona->requestApplicationConfigSync(); resizePanel(); } int PanelView::length() const { return qMax(1, m_contentLength); } void PanelView::setLength(int value) { if (value == m_contentLength) { return; } m_contentLength = value; resizePanel(); } int PanelView::maximumLength() const { return m_maxLength; } void PanelView::setMaximumLength(int length) { if (length == m_maxLength) { return; } if (m_minLength > length) { setMinimumLength(length); } config().writeEntry("maxLength", length); m_maxLength = length; emit maximumLengthChanged(); m_corona->requestApplicationConfigSync(); resizePanel(); } int PanelView::minimumLength() const { return m_minLength; } void PanelView::setMinimumLength(int length) { if (length == m_minLength) { return; } if (m_maxLength < length) { setMaximumLength(length); } config().writeEntry("minLength", length); m_minLength = length; emit minimumLengthChanged(); m_corona->requestApplicationConfigSync(); resizePanel(); } int PanelView::distance() const { return m_distance; } void PanelView::setDistance(int dist) { if (m_distance == dist) { return; } m_distance = dist; emit distanceChanged(); positionPanel(); } Plasma::Types::BackgroundHints PanelView::backgroundHints() const { return m_backgroundHints; } void PanelView::setBackgroundHints(Plasma::Types::BackgroundHints hint) { if (m_backgroundHints == hint) { return; } m_backgroundHints = hint; emit backgroundHintsChanged(); } Plasma::FrameSvg::EnabledBorders PanelView::enabledBorders() const { return m_enabledBorders; } void PanelView::setVisibilityMode(PanelView::VisibilityMode mode) { if (m_visibilityMode == mode) { return; } m_visibilityMode = mode; disconnect(containment(), &Plasma::Applet::activated, this, &PanelView::showTemporarily); if (edgeActivated()) { connect(containment(), &Plasma::Applet::activated, this, &PanelView::showTemporarily); } if (config().isValid() && config().parent().isValid()) { //panelVisibility is not resolution dependent config().parent().writeEntry("panelVisibility", (int)mode); m_corona->requestApplicationConfigSync(); } visibilityModeToWayland(); updateStruts(); emit visibilityModeChanged(); restoreAutoHide(); } void PanelView::visibilityModeToWayland() { if (!m_shellSurface) { return; } KWayland::Client::PlasmaShellSurface::PanelBehavior behavior; switch (m_visibilityMode) { case NormalPanel: behavior = KWayland::Client::PlasmaShellSurface::PanelBehavior::AlwaysVisible; break; case AutoHide: behavior = KWayland::Client::PlasmaShellSurface::PanelBehavior::AutoHide; break; case LetWindowsCover: behavior = KWayland::Client::PlasmaShellSurface::PanelBehavior::WindowsCanCover; break; case WindowsGoBelow: behavior = KWayland::Client::PlasmaShellSurface::PanelBehavior::WindowsGoBelow; break; default: Q_UNREACHABLE(); return; } m_shellSurface->setPanelBehavior(behavior); } PanelView::VisibilityMode PanelView::visibilityMode() const { return m_visibilityMode; } void PanelView::positionPanel() { if (!containment()) { return; } if (!m_initCompleted) { return; } KWindowEffects::SlideFromLocation slideLocation = KWindowEffects::NoEdge; switch (containment()->location()) { case Plasma::Types::TopEdge: containment()->setFormFactor(Plasma::Types::Horizontal); slideLocation = KWindowEffects::TopEdge; break; case Plasma::Types::LeftEdge: containment()->setFormFactor(Plasma::Types::Vertical); slideLocation = KWindowEffects::LeftEdge; break; case Plasma::Types::RightEdge: containment()->setFormFactor(Plasma::Types::Vertical); slideLocation = KWindowEffects::RightEdge; break; case Plasma::Types::BottomEdge: default: containment()->setFormFactor(Plasma::Types::Horizontal); slideLocation = KWindowEffects::BottomEdge; break; } const QPoint pos = geometryByDistance(m_distance).topLeft(); setPosition(pos); if (m_shellSurface) { m_shellSurface->setPosition(pos); } KWindowEffects::slideWindow(winId(), slideLocation, -1); } QRect PanelView::geometryByDistance(int distance) const { QScreen *s = m_screenToFollow; QPoint position; const QRect screenGeometry = s->geometry(); switch (containment()->location()) { case Plasma::Types::TopEdge: switch (m_alignment) { case Qt::AlignCenter: position = QPoint(QPoint(screenGeometry.center().x(), screenGeometry.top()) + QPoint(m_offset - width()/2, distance)); break; case Qt::AlignRight: position = QPoint(QPoint(screenGeometry.x() + screenGeometry.width(), screenGeometry.y()) - QPoint(m_offset + width(), distance)); break; case Qt::AlignLeft: default: position = QPoint(screenGeometry.topLeft() + QPoint(m_offset, distance)); } break; case Plasma::Types::LeftEdge: switch (m_alignment) { case Qt::AlignCenter: position = QPoint(QPoint(screenGeometry.left(), screenGeometry.center().y()) + QPoint(distance, m_offset - height()/2)); break; case Qt::AlignRight: position = QPoint(QPoint(screenGeometry.left(), screenGeometry.y() + screenGeometry.height()) - QPoint(distance, m_offset + height())); break; case Qt::AlignLeft: default: position = QPoint(screenGeometry.topLeft() + QPoint(distance, m_offset)); } break; case Plasma::Types::RightEdge: switch (m_alignment) { case Qt::AlignCenter: // Never use rect.right(); for historical reasons it returns left() + width() - 1; see http://doc.qt.io/qt-5/qrect.html#right position = QPoint(QPoint(screenGeometry.x() + screenGeometry.width(), screenGeometry.center().y()) - QPoint(thickness() + distance, 0) + QPoint(0, m_offset - height()/2)); break; case Qt::AlignRight: position = QPoint(QPoint(screenGeometry.x() + screenGeometry.width(), screenGeometry.y() + screenGeometry.height()) - QPoint(thickness() + distance, 0) - QPoint(0, m_offset + height())); break; case Qt::AlignLeft: default: position = QPoint(QPoint(screenGeometry.x() + screenGeometry.width(), screenGeometry.y()) - QPoint(thickness() + distance, 0) + QPoint(0, m_offset)); } break; case Plasma::Types::BottomEdge: default: switch (m_alignment) { case Qt::AlignCenter: position = QPoint(QPoint(screenGeometry.center().x(), screenGeometry.bottom() - thickness() - distance) + QPoint(m_offset - width()/2, 1)); break; case Qt::AlignRight: position = QPoint(screenGeometry.bottomRight() - QPoint(0, thickness() + distance) - QPoint(m_offset + width(), -1)); break; case Qt::AlignLeft: default: position = QPoint(screenGeometry.bottomLeft() - QPoint(0, thickness() + distance) + QPoint(m_offset, 1)); } } QRect ret = formFactor() == Plasma::Types::Vertical ? QRect(position, QSize(thickness(), height())) : QRect(position, QSize(width(), thickness())); ret = ret.intersected(screenGeometry); return ret; } void PanelView::resizePanel() { if (!m_initCompleted) { return; } QSize targetSize; QSize targetMinSize; QSize targetMaxSize; if (formFactor() == Plasma::Types::Vertical) { const int minSize = qMax(MINSIZE, m_minLength); const int maxSize = qMin(m_maxLength, m_screenToFollow->size().height() - m_offset); targetMinSize = QSize(thickness(), minSize); targetMaxSize = QSize(thickness(), maxSize); targetSize = QSize(thickness(), qBound(minSize, m_contentLength, maxSize)); } else { const int minSize = qMax(MINSIZE, m_minLength); const int maxSize = qMin(m_maxLength, m_screenToFollow->size().width() - m_offset); targetMinSize = QSize(minSize, thickness()); targetMaxSize = QSize(maxSize, thickness()); targetSize = QSize(qBound(minSize, m_contentLength, maxSize), thickness()); } if (minimumSize() != targetMinSize) { setMinimumSize(targetMinSize); } if (maximumSize() != targetMaxSize) { setMaximumSize(targetMaxSize); } if (size() != targetSize) { resize(targetSize); } //position will be updated implicitly from resizeEvent } void PanelView::restore() { if (!containment()) { return; } //defaults, may be altered by values written by the scripting in startup phase const int defaultOffset = 0; const int defaultAlignment = Qt::AlignLeft; //alignment is not resolution dependent //but if fails read it from the resolution dependent one as //the place for this config key is changed in Plasma 5.9 setAlignment((Qt::Alignment)config().parent().readEntry("alignment", config().readEntry("alignment", defaultAlignment))); m_offset = config().readEntry("offset", defaultOffset); if (m_alignment != Qt::AlignCenter) { m_offset = qMax(0, m_offset); } const int defaultThickness = 30; setThickness(config().readEntry("thickness", defaultThickness)); const QSize screenSize = m_screenToFollow->size(); setMinimumSize(QSize(-1, -1)); //FIXME: an invalid size doesn't work with QWindows setMaximumSize(screenSize); const int side = containment()->formFactor() == Plasma::Types::Vertical ? screenSize.height() : screenSize.width(); const int maxSize = side - m_offset; m_maxLength = qBound(MINSIZE, config().readEntry("maxLength", side), maxSize); m_minLength = qBound(MINSIZE, config().readEntry("minLength", side), maxSize); //panelVisibility is not resolution dependent //but if fails read it from the resolution dependent one as //the place for this config key is changed in Plasma 5.9 setVisibilityMode((VisibilityMode)config().parent().readEntry("panelVisibility", config().readEntry("panelVisibility", (int)NormalPanel))); m_initCompleted = true; resizePanel(); positionPanel(); emit maximumLengthChanged(); emit minimumLengthChanged(); emit offsetChanged(); emit alignmentChanged(); //::restore might have been called directly before the timer fires // at which point we don't still need the timer m_positionPaneltimer.stop(); } void PanelView::showConfigurationInterface(Plasma::Applet *applet) { if (!applet || !applet->containment()) { return; } Plasma::Containment *cont = qobject_cast(applet); const bool isPanelConfig = (cont && cont == containment() && cont->isContainment()); if (m_panelConfigView) { if (m_panelConfigView->applet() == applet) { if (isPanelConfig) { // toggles panel controller, whereas applet config is always brought to front if (m_panelConfigView->isVisible()) { m_panelConfigView->hide(); } else { m_panelConfigView->show(); } return; } m_panelConfigView->show(); m_panelConfigView->requestActivate(); return; } m_panelConfigView->hide(); m_panelConfigView->deleteLater(); } if (isPanelConfig) { m_panelConfigView = new PanelConfigView(cont, this); } else { m_panelConfigView = new PlasmaQuick::ConfigView(applet); } m_panelConfigView.data()->init(); m_panelConfigView.data()->show(); m_panelConfigView.data()->requestActivate(); if (isPanelConfig) { KWindowSystem::setState(m_panelConfigView.data()->winId(), NET::SkipTaskbar | NET::SkipPager); } } void PanelView::restoreAutoHide() { bool autoHide = true; disconnect(m_transientWindowVisibleWatcher); if (!edgeActivated()) { autoHide = false; } else if (m_containsMouse) { autoHide = false; } else if (containment() && containment()->isUserConfiguring()) { autoHide = false; } else if (containment() && containment()->status() >= Plasma::Types::NeedsAttentionStatus && containment()->status() != Plasma::Types::HiddenStatus) { autoHide = false; } else { for(QWindow *window: qApp->topLevelWindows()) { if (window->transientParent() == this && window->isVisible()) { m_transientWindowVisibleWatcher = connect(window, &QWindow::visibleChanged, this, &PanelView::restoreAutoHide); autoHide = false; break; //if multiple are open, we will re-evaluate this expression after the first closes } } } setAutoHideEnabled(autoHide); } void PanelView::setAutoHideEnabled(bool enabled) { #if HAVE_X11 if (KWindowSystem::isPlatformX11()) { xcb_connection_t *c = QX11Info::connection(); const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_SCREEN_EDGE_SHOW"); xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData()); QScopedPointer atom(xcb_intern_atom_reply(c, atomCookie, nullptr)); if (!atom) { return; } if (!enabled) { xcb_delete_property(c, winId(), atom->atom); return; } KWindowEffects::SlideFromLocation slideLocation = KWindowEffects::NoEdge; uint32_t value = 0; switch (location()) { case Plasma::Types::TopEdge: value = 0; slideLocation = KWindowEffects::TopEdge; break; case Plasma::Types::RightEdge: value = 1; slideLocation = KWindowEffects::RightEdge; break; case Plasma::Types::BottomEdge: value = 2; slideLocation = KWindowEffects::BottomEdge; break; case Plasma::Types::LeftEdge: value = 3; slideLocation = KWindowEffects::LeftEdge; break; case Plasma::Types::Floating: default: value = 4; break; } int hideType = 0; if (m_visibilityMode == LetWindowsCover) { hideType = 1; } value |= hideType << 8; xcb_change_property(c, XCB_PROP_MODE_REPLACE, winId(), atom->atom, XCB_ATOM_CARDINAL, 32, 1, &value); KWindowEffects::slideWindow(winId(), slideLocation, -1); } #endif if (m_shellSurface && m_visibilityMode == PanelView::AutoHide) { if (enabled) { m_shellSurface->requestHideAutoHidingPanel(); } else { m_shellSurface->requestShowAutoHidingPanel(); } } } void PanelView::resizeEvent(QResizeEvent *ev) { updateEnabledBorders(); //don't setGeometry() to make really sure we aren't doing a resize loop const QPoint pos = geometryByDistance(m_distance).topLeft(); setPosition(pos); if (m_shellSurface) { m_shellSurface->setPosition(pos); } m_strutsTimer.start(STRUTSTIMERDELAY); emit m_corona->availableScreenRegionChanged(); PlasmaQuick::ContainmentView::resizeEvent(ev); #if PLASMA_VERSION < QT_VERSION_CHECK(5,59,0) updateMask(); #endif } void PanelView::moveEvent(QMoveEvent *ev) { updateEnabledBorders(); m_strutsTimer.start(STRUTSTIMERDELAY); PlasmaQuick::ContainmentView::moveEvent(ev); #if PLASMA_VERSION < QT_VERSION_CHECK(5,59,0) updateMask(); #endif } void PanelView::integrateScreen() { connect(m_screenToFollow.data(), &QScreen::geometryChanged, this, &PanelView::restore); - themeChanged(); + updateMask(); KWindowSystem::setOnAllDesktops(winId(), true); KWindowSystem::setType(winId(), NET::Dock); #if HAVE_X11 QXcbWindowFunctions::setWmWindowType(this, QXcbWindowFunctions::Dock); #endif if (m_shellSurface) { m_shellSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::Panel); m_shellSurface->setSkipTaskbar(true); } setVisibilityMode(m_visibilityMode); if (containment()) { containment()->reactToScreenChange(); } } void PanelView::showEvent(QShowEvent *event) { PlasmaQuick::ContainmentView::showEvent(event); integrateScreen(); } void PanelView::setScreenToFollow(QScreen *screen) { if (screen == m_screenToFollow) { return; } if (!screen) { return; } /*connect(screen, &QObject::destroyed, this, [this]() { if (PanelView::screen()) { m_screenToFollow = PanelView::screen(); adaptToScreen(); } });*/ m_screenToFollow = screen; setScreen(screen); adaptToScreen(); } QScreen *PanelView::screenToFollow() const { return m_screenToFollow; } void PanelView::adaptToScreen() { emit screenToFollowChanged(m_screenToFollow); m_lastScreen = m_screenToFollow; if (!m_screenToFollow) { return; } integrateScreen(); showTemporarily(); m_positionPaneltimer.start(); } bool PanelView::event(QEvent *e) { switch (e->type()) { case QEvent::Enter: m_containsMouse = true; if (edgeActivated()) { m_unhideTimer.stop(); } break; case QEvent::Leave: m_containsMouse = false; if (edgeActivated()) { m_unhideTimer.start(); } break; /*Fitt's law: if the containment has margins, and the mouse cursor clicked * on the mouse edge, forward the click in the containment boundaries */ case QEvent::MouseMove: case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: { QMouseEvent *me = static_cast(e); //first, don't mess with position if the cursor is actually outside the view: //somebody is doing a click and drag that must not break when the cursor i outside if (geometry().contains(QCursor::pos(screenToFollow()))) { if (!containmentContainsPosition(me->windowPos())) { auto me2 = new QMouseEvent(me->type(), positionAdjustedForContainment(me->windowPos()), positionAdjustedForContainment(me->windowPos()), positionAdjustedForContainment(me->windowPos()) + position(), me->button(), me->buttons(), me->modifiers()); QCoreApplication::postEvent(this, me2); return true; } } else { // default handling if current mouse position is outside the panel return ContainmentView::event(e); } break; } case QEvent::Wheel: { QWheelEvent *we = static_cast(e); if (!containmentContainsPosition(we->pos())) { auto we2 = new QWheelEvent(positionAdjustedForContainment(we->pos()), positionAdjustedForContainment(we->pos()) + position(), we->pixelDelta(), we->angleDelta(), we->delta(), we->orientation(), we->buttons(), we->modifiers(), we->phase()); QCoreApplication::postEvent(this, we2); return true; } break; } case QEvent::DragEnter: { QDragEnterEvent *de = static_cast(e); if (!containmentContainsPosition(de->pos())) { auto de2 = new QDragEnterEvent(positionAdjustedForContainment(de->pos()).toPoint(), de->possibleActions(), de->mimeData(), de->mouseButtons(), de->keyboardModifiers()); QCoreApplication::postEvent(this, de2); return true; } break; } //DragLeave just works case QEvent::DragLeave: break; case QEvent::DragMove: { QDragMoveEvent *de = static_cast(e); if (!containmentContainsPosition(de->pos())) { auto de2 = new QDragMoveEvent(positionAdjustedForContainment(de->pos()).toPoint(), de->possibleActions(), de->mimeData(), de->mouseButtons(), de->keyboardModifiers()); QCoreApplication::postEvent(this, de2); return true; } break; } case QEvent::Drop: { QDropEvent *de = static_cast(e); if (!containmentContainsPosition(de->pos())) { auto de2 = new QDropEvent(positionAdjustedForContainment(de->pos()).toPoint(), de->possibleActions(), de->mimeData(), de->mouseButtons(), de->keyboardModifiers()); QCoreApplication::postEvent(this, de2); return true; } break; } case QEvent::Hide: { if (m_panelConfigView && m_panelConfigView.data()->isVisible()) { m_panelConfigView.data()->hide(); } m_containsMouse = false; break; } case QEvent::PlatformSurface: switch (static_cast(e)->surfaceEventType()) { case QPlatformSurfaceEvent::SurfaceCreated: setupWaylandIntegration(); PanelShadows::self()->addWindow(this, enabledBorders()); break; case QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed: delete m_shellSurface; m_shellSurface = nullptr; PanelShadows::self()->removeWindow(this); break; } break; default: break; } return ContainmentView::event(e); } bool PanelView::containmentContainsPosition(const QPointF &point) const { QQuickItem *containmentItem = containment()->property("_plasma_graphicObject").value(); if (!containmentItem) { return false; } return QRectF(containmentItem->mapToScene(QPoint(0,0)), QSizeF(containmentItem->width(), containmentItem->height())).contains(point); } QPointF PanelView::positionAdjustedForContainment(const QPointF &point) const { QQuickItem *containmentItem = containment()->property("_plasma_graphicObject").value(); if (!containmentItem) { return point; } QRectF containmentRect(containmentItem->mapToScene(QPoint(0,0)), QSizeF(containmentItem->width(), containmentItem->height())); return QPointF(qBound(containmentRect.left() + 2, point.x(), containmentRect.right() - 2), qBound(containmentRect.top() + 2, point.y(), containmentRect.bottom() - 2)); } void PanelView::updateMask() { - QRegion mask; + if (m_backgroundHints == Plasma::Types::NoBackground) { + KWindowEffects::enableBlurBehind(winId(), false); + KWindowEffects::enableBackgroundContrast(winId(), false); + setMask(QRegion()); + } else { + QRegion mask; - QQuickItem *rootObject = this->rootObject(); - if (rootObject) { - const QVariant maskProperty = rootObject->property("panelMask"); - if (static_cast(maskProperty.type()) == QMetaType::QRegion) { - mask = maskProperty.value(); + QQuickItem *rootObject = this->rootObject(); + if (rootObject) { + const QVariant maskProperty = rootObject->property("panelMask"); + if (static_cast(maskProperty.type()) == QMetaType::QRegion) { + mask = maskProperty.value(); + } } - } - setMask(mask); + KWindowEffects::enableBlurBehind(winId(), m_theme.blurBehindEnabled(), mask); + KWindowEffects::enableBackgroundContrast(winId(), m_theme.backgroundContrastEnabled(), + m_theme.backgroundContrast(), + m_theme.backgroundIntensity(), + m_theme.backgroundSaturation(), + mask); + + if (KWindowSystem::compositingActive()) { + setMask(QRegion()); + } else { + setMask(mask); + } + } } bool PanelView::canSetStrut() const { #if HAVE_X11 if (!KWindowSystem::isPlatformX11()) { return true; } // read the wm name, need to do this every time which means a roundtrip unfortunately // but WM might have changed NETRootInfo rootInfo(QX11Info::connection(), NET::Supported | NET::SupportingWMCheck); if (qstricmp(rootInfo.wmName(), "KWin") == 0) { // KWin since 5.7 can handle this fine, so only exclude for other window managers return true; } const QRect thisScreen = screen()->geometry(); const int numScreens = corona()->numScreens(); if (numScreens < 2) { return true; } //Extended struts against a screen edge near to another screen are really harmful, so windows maximized under the panel is a lesser pain //TODO: force "windows can cover" in those cases? const auto screenIds = m_corona->screenIds(); for (int id : screenIds) { if (id == containment()->screen()) { continue; } const QRect otherScreen = corona()->screenGeometry(id); if (!otherScreen.isValid()) { continue; } switch (location()) { case Plasma::Types::TopEdge: if (otherScreen.bottom() <= thisScreen.top()) { return false; } break; case Plasma::Types::BottomEdge: if (otherScreen.top() >= thisScreen.bottom()) { return false; } break; case Plasma::Types::RightEdge: if (otherScreen.left() >= thisScreen.right()) { return false; } break; case Plasma::Types::LeftEdge: if (otherScreen.right() <= thisScreen.left()) { return false; } break; default: return false; } } return true; #else return true; #endif } void PanelView::updateStruts() { if (!containment() || containment()->isUserConfiguring() || !m_screenToFollow) { return; } NETExtendedStrut strut; if (m_visibilityMode == NormalPanel) { const QRect thisScreen = m_screenToFollow->geometry(); // QScreen::virtualGeometry() is very unreliable (Qt 5.5) const QRect wholeScreen = QRect(QPoint(0, 0), m_screenToFollow->virtualSize()); if (!canSetStrut()) { KWindowSystem::setExtendedStrut(winId(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); return; } // extended struts are to the combined screen geoms, not the single screen int leftOffset = thisScreen.x(); int rightOffset = wholeScreen.right() - thisScreen.right(); int bottomOffset = wholeScreen.bottom() - thisScreen.bottom(); // qDebug() << "screen l/r/b/t offsets are:" << leftOffset << rightOffset << bottomOffset << topOffset << location(); int topOffset = thisScreen.top(); switch (location()) { case Plasma::Types::TopEdge: strut.top_width = thickness() + topOffset; strut.top_start = x(); strut.top_end = x() + width() - 1; // qDebug() << "setting top edge to" << strut.top_width << strut.top_start << strut.top_end; break; case Plasma::Types::BottomEdge: strut.bottom_width = thickness() + bottomOffset; strut.bottom_start = x(); strut.bottom_end = x() + width() - 1; // qDebug() << "setting bottom edge to" << strut.bottom_width << strut.bottom_start << strut.bottom_end; break; case Plasma::Types::RightEdge: strut.right_width = thickness() + rightOffset; strut.right_start = y(); strut.right_end = y() + height() - 1; // qDebug() << "setting right edge to" << strut.right_width << strut.right_start << strut.right_end; break; case Plasma::Types::LeftEdge: strut.left_width = thickness() + leftOffset; strut.left_start = y(); strut.left_end = y() + height() - 1; // qDebug() << "setting left edge to" << strut.left_width << strut.left_start << strut.left_end; break; default: //qDebug() << "where are we?"; break; } } KWindowSystem::setExtendedStrut(winId(), strut.left_width, strut.left_start, strut.left_end, strut.right_width, strut.right_start, strut.right_end, strut.top_width, strut.top_start, strut.top_end, strut.bottom_width, strut.bottom_start, strut.bottom_end); } -void PanelView::themeChanged() -{ - if (m_backgroundHints == Plasma::Types::NoBackground) { - KWindowEffects::enableBlurBehind(winId(), false); - KWindowEffects::enableBackgroundContrast(winId(), false); - } else { - KWindowEffects::enableBlurBehind(winId(), m_theme.blurBehindEnabled()); - KWindowEffects::enableBackgroundContrast(winId(), m_theme.backgroundContrastEnabled(), - m_theme.backgroundContrast(), - m_theme.backgroundIntensity(), - m_theme.backgroundSaturation()); - } - -#if PLASMA_VERSION < QT_VERSION_CHECK(5,59,0) - updateMask(); -#endif -} - void PanelView::containmentChanged() { restore(); connect(containment(), &Plasma::Containment::userConfiguringChanged, this, [this](bool configuring){ if (configuring) { showTemporarily(); } else { m_unhideTimer.start(); updateStruts(); } }); connect(containment(), SIGNAL(statusChanged(Plasma::Types::ItemStatus)), SLOT(statusChanged(Plasma::Types::ItemStatus))); connect(containment(), &Plasma::Applet::appletDeleted, this, [this] { //containment()->destroyed() is true only when the user deleted it //so the config is to be thrown away, not during shutdown if (containment()->destroyed()) { KConfigGroup views(m_corona->applicationConfig(), "PlasmaViews"); for (auto grp : views.groupList()) { if (grp.contains(QRegExp(QStringLiteral("Panel ") + QString::number(containment()->id()) + QStringLiteral("$")))) { qDebug() << "Panel" << containment()->id() << "removed by user"; views.deleteGroup(grp); } views.sync(); } } }); } void PanelView::handleQmlStatusChange(QQmlComponent::Status status) { if (status != QQmlComponent::Ready) { return; } QQuickItem *rootObject = this->rootObject(); if (rootObject) { disconnect(this, &QuickViewSharedEngine::statusChanged, this, &PanelView::handleQmlStatusChange); const QVariant maskProperty = rootObject->property("panelMask"); if (static_cast(maskProperty.type()) == QMetaType::QRegion) { connect(rootObject, SIGNAL(panelMaskChanged()), this, SLOT(updateMask())); updateMask(); } } } void PanelView::statusChanged(Plasma::Types::ItemStatus status) { if (status == Plasma::Types::NeedsAttentionStatus) { showTemporarily(); setFlags(flags() | Qt::WindowDoesNotAcceptFocus); } else if (status == Plasma::Types::AcceptingInputStatus) { setFlags(flags() & ~Qt::WindowDoesNotAcceptFocus); KWindowSystem::forceActiveWindow(winId()); } else { restoreAutoHide(); setFlags(flags() | Qt::WindowDoesNotAcceptFocus); } } void PanelView::showTemporarily() { setAutoHideEnabled(false); QTimer * t = new QTimer(this); t->setSingleShot(true); t->setInterval(3000); connect(t, &QTimer::timeout, this, &PanelView::restoreAutoHide); connect(t, &QTimer::timeout, t, &QObject::deleteLater); t->start(); } void PanelView::screenDestroyed(QObject* ) { // NOTE: this is overriding the screen destroyed slot, we need to do this because // otherwise Qt goes mental and starts moving our panels. See: // https://codereview.qt-project.org/#/c/88351/ // if(screen == this->m_screenToFollow) { // DO NOTHING, panels are moved by ::readaptToScreen // } } void PanelView::setupWaylandIntegration() { if (m_shellSurface) { // already setup return; } if (ShellCorona *c = qobject_cast(corona())) { using namespace KWayland::Client; PlasmaShell *interface = c->waylandPlasmaShellInterface(); if (!interface) { return; } Surface *s = Surface::fromWindow(this); if (!s) { return; } m_shellSurface = interface->createSurface(s, this); } } bool PanelView::edgeActivated() const { return m_visibilityMode == PanelView::AutoHide || m_visibilityMode == LetWindowsCover; } void PanelView::updateEnabledBorders() { Plasma::FrameSvg::EnabledBorders borders = Plasma::FrameSvg::AllBorders; if (m_backgroundHints == Plasma::Types::NoBackground) { borders = Plasma::FrameSvg::NoBorder; } else { switch (location()) { case Plasma::Types::TopEdge: borders &= ~Plasma::FrameSvg::TopBorder; break; case Plasma::Types::LeftEdge: borders &= ~Plasma::FrameSvg::LeftBorder; break; case Plasma::Types::RightEdge: borders &= ~Plasma::FrameSvg::RightBorder; break; case Plasma::Types::BottomEdge: borders &= ~Plasma::FrameSvg::BottomBorder; break; default: break; } if (x() <= m_screenToFollow->geometry().x()) { borders &= ~Plasma::FrameSvg::LeftBorder; } if (x() + width() >= m_screenToFollow->geometry().x() + m_screenToFollow->geometry().width()) { borders &= ~Plasma::FrameSvg::RightBorder; } if (y() <= m_screenToFollow->geometry().y()) { borders &= ~Plasma::FrameSvg::TopBorder; } if (y() + height() >= m_screenToFollow->geometry().y() + m_screenToFollow->geometry().height()) { borders &= ~Plasma::FrameSvg::BottomBorder; } } if (m_enabledBorders != borders) { if (m_backgroundHints == Plasma::Types::NoBackground) { PanelShadows::self()->removeWindow(this); } else { PanelShadows::self()->setEnabledBorders(this, borders); } m_enabledBorders = borders; emit enabledBordersChanged(); } } #include "moc_panelview.cpp" diff --git a/shell/panelview.h b/shell/panelview.h index d776f5544..c9defb890 100644 --- a/shell/panelview.h +++ b/shell/panelview.h @@ -1,244 +1,243 @@ /* * Copyright 2013 Marco Martin * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef PANELVIEW_H #define PANELVIEW_H #include #include #include #include #include class ShellCorona; namespace KWayland { namespace Client { class PlasmaShellSurface; } } class PanelView : public PlasmaQuick::ContainmentView { Q_OBJECT /** * Alignment of the panel: when not fullsize it can be aligned at left, * right or center of the screen (left and right work as top/bottom * too for vertical panels) */ Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged) /** * how much the panel is moved from the left/right/center anchor point */ Q_PROPERTY(int offset READ offset WRITE setOffset NOTIFY offsetChanged) /** * height of horizontal panels, width of vertical panels */ Q_PROPERTY(int thickness READ thickness WRITE setThickness NOTIFY thicknessChanged) /** * width of horizontal panels, height of vertical panels */ Q_PROPERTY(int length READ length WRITE setLength NOTIFY lengthChanged) /** * if the panel resizes itself, never resize more than that */ Q_PROPERTY(int maximumLength READ maximumLength WRITE setMaximumLength NOTIFY maximumLengthChanged) /** * if the panel resizes itself, never resize less than that */ Q_PROPERTY(int minimumLength READ minimumLength WRITE setMinimumLength NOTIFY minimumLengthChanged) /** * how much the panel is distant for the screen edge: used by the panel controller to drag it around */ Q_PROPERTY(int distance READ distance WRITE setDistance NOTIFY distanceChanged) /** * support NoBackground in order to disable blur/contrast effects and remove * the panel shadows * @since 5.9 */ Q_PROPERTY(Plasma::Types::BackgroundHints backgroundHints WRITE setBackgroundHints READ backgroundHints NOTIFY backgroundHintsChanged) /** * The borders that should have a shadow * @since 5.7 */ Q_PROPERTY(Plasma::FrameSvg::EnabledBorders enabledBorders READ enabledBorders NOTIFY enabledBordersChanged) /** * information about the screen in which the panel is in */ Q_PROPERTY(QScreen *screenToFollow READ screenToFollow WRITE setScreenToFollow NOTIFY screenToFollowChanged) /** * how the panel behaves, visible, autohide etc. */ Q_PROPERTY(VisibilityMode visibilityMode READ visibilityMode WRITE setVisibilityMode NOTIFY visibilityModeChanged) public: enum VisibilityMode { NormalPanel = 0, /** default, always visible panel, the windowmanager reserves a places for it */ AutoHide, /**the panel will be shownn only if the mouse cursor is on screen edges */ LetWindowsCover, /** always visible, windows will go over the panel, no area reserved */ WindowsGoBelow /** always visible, windows will go under the panel, no area reserved */ }; Q_ENUM(VisibilityMode) explicit PanelView(ShellCorona *corona, QScreen *targetScreen = nullptr, QWindow *parent = nullptr); ~PanelView() override; KConfigGroup config() const override; Q_INVOKABLE void maximize(); Qt::Alignment alignment() const; void setAlignment(Qt::Alignment alignment); int offset() const; void setOffset(int offset); int thickness() const; void setThickness(int thickness); int length() const; void setLength(int value); int maximumLength() const; void setMaximumLength(int length); int minimumLength() const; void setMinimumLength(int length); int distance() const; void setDistance(int dist); Plasma::Types::BackgroundHints backgroundHints() const; void setBackgroundHints(Plasma::Types::BackgroundHints hint); Plasma::FrameSvg::EnabledBorders enabledBorders() const; VisibilityMode visibilityMode() const; void setVisibilityMode(PanelView::VisibilityMode mode); /** * @returns the geometry of the panel given a distance */ QRect geometryByDistance(int distance) const; /* Shared with script/panel.cpp */ static KConfigGroup panelConfig(ShellCorona *corona, Plasma::Containment *containment, QScreen *screen); void updateStruts(); /*This is different from screen() as is always there, even if the window is temporarly outside the screen or if is hidden: only plasmashell will ever change this property, unlike QWindow::screen()*/ void setScreenToFollow(QScreen* screen); QScreen* screenToFollow() const; protected: void resizeEvent(QResizeEvent *ev) override; void showEvent(QShowEvent *event) override; void moveEvent(QMoveEvent *ev) override; bool event(QEvent *e) override; Q_SIGNALS: void alignmentChanged(); void offsetChanged(); void screenGeometryChanged(); void thicknessChanged(); void lengthChanged(); void maximumLengthChanged(); void minimumLengthChanged(); void distanceChanged(); void backgroundHintsChanged(); void enabledBordersChanged(); //QWindow does not have a property for screen. Adding this property requires re-implementing the signal void screenToFollowChanged(QScreen *screen); void visibilityModeChanged(); protected Q_SLOTS: /** * It will be called when the configuration is requested */ void showConfigurationInterface(Plasma::Applet *applet) override; private Q_SLOTS: - void themeChanged(); void positionPanel(); void restore(); void setAutoHideEnabled(bool autoHideEnabled); void showTemporarily(); void containmentChanged(); void statusChanged(Plasma::Types::ItemStatus); void restoreAutoHide(); void screenDestroyed(QObject* screen); void adaptToScreen(); void handleQmlStatusChange(QQmlComponent::Status status); void updateMask(); private: void resizePanel(); void integrateScreen(); bool containmentContainsPosition(const QPointF &point) const; QPointF positionAdjustedForContainment(const QPointF &point) const; void setupWaylandIntegration(); void visibilityModeToWayland(); bool edgeActivated() const; void updateEnabledBorders(); bool canSetStrut() const; int m_offset; int m_maxLength; int m_minLength; int m_contentLength; int m_distance; int m_thickness; bool m_initCompleted; bool m_containsMouse = false; Qt::Alignment m_alignment; QPointer m_panelConfigView; ShellCorona *m_corona; QTimer m_strutsTimer; VisibilityMode m_visibilityMode; Plasma::Theme m_theme; QTimer m_positionPaneltimer; QTimer m_unhideTimer; Plasma::Types::BackgroundHints m_backgroundHints; Plasma::FrameSvg::EnabledBorders m_enabledBorders = Plasma::FrameSvg::AllBorders; KWayland::Client::PlasmaShellSurface *m_shellSurface; QPointer m_lastScreen; QPointer m_screenToFollow; QMetaObject::Connection m_transientWindowVisibleWatcher; static const int STRUTSTIMERDELAY = 200; }; #endif // PANELVIEW_H diff --git a/startkde/CMakeLists.txt b/startkde/CMakeLists.txt index 6a1a2121b..7fc04b486 100644 --- a/startkde/CMakeLists.txt +++ b/startkde/CMakeLists.txt @@ -1,20 +1,37 @@ add_subdirectory(kcminit) -add_subdirectory(kstartupconfig) add_subdirectory(ksyncdbusenv) add_subdirectory(waitforname) -add_subdirectory(kcheckrunning) + +add_definitions(-DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII) +add_definitions(-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT) +add_definitions(-DQT_NO_URL_CAST_FROM_STRING) + +qt5_add_dbus_interface( + startplasma_SRCS + ${CMAKE_SOURCE_DIR}/ksplash/ksplashqml/org.kde.KSplash.xml + ksplashinterface +) + +add_executable(startplasma-x11 startplasma.cpp startplasma-x11.cpp kcheckrunning/kcheckrunning.cpp ${startplasma_SRCS}) +add_executable(startplasma-wayland startplasma.cpp startplasma-wayland.cpp ${startplasma_SRCS}) +add_executable(startplasma-waylandsession startplasma.cpp startplasma-waylandsession.cpp ${startplasma_SRCS}) + +target_include_directories(startplasma-x11 PRIVATE ${X11_X11_INCLUDE_PATH}) +target_link_libraries(startplasma-x11 PRIVATE Qt5::Core Qt5::DBus KF5::ConfigCore + ${X11_X11_LIB} # for kcheckrunning +) +target_link_libraries(startplasma-wayland PRIVATE Qt5::Core Qt5::DBus KF5::ConfigCore) +target_link_libraries(startplasma-waylandsession PRIVATE Qt5::Core Qt5::DBus KF5::ConfigCore) #FIXME: reconsider, looks fishy if(NOT CMAKE_INSTALL_PREFIX STREQUAL "/usr") - set(EXPORT_XCURSOR_PATH "XCURSOR_PATH=${KDE_INSTALL_FULL_DATAROOTDIR}/icons:$XCURSOR_PATH\":~/.icons:/usr/share/icons:/usr/share/pixmaps:/usr/X11R6/lib/X11/icons\"; export XCURSOR_PATH") + set_property(SOURCE startplasma.cpp APPEND PROPERTY COMPILE_DEFINITIONS + XCURSOR_PATH="${KDE_INSTALL_FULL_DATAROOTDIR}/icons:$XCURSOR_PATH:~/.icons:/usr/share/icons:/usr/share/pixmaps:/usr/X11R6/lib/X11/icons") endif() -configure_file(startkde.cmake ${CMAKE_CURRENT_BINARY_DIR}/startkde @ONLY) -configure_file(startplasmacompositor.cmake ${CMAKE_CURRENT_BINARY_DIR}/startplasmacompositor @ONLY) -configure_file(startplasma.cmake ${CMAKE_CURRENT_BINARY_DIR}/startplasma @ONLY) +configure_file(config-startplasma.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-startplasma.h) -if(NOT WIN32) - install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/startkde DESTINATION ${KDE_INSTALL_BINDIR}) - install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/startplasmacompositor DESTINATION ${KDE_INSTALL_BINDIR}) - install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/startplasma DESTINATION ${KDE_INSTALL_LIBEXECDIR}) -endif() +install(TARGETS startplasma-x11 ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) +install(TARGETS startplasma-wayland ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) +install(TARGETS startplasma-waylandsession DESTINATION ${KDE_INSTALL_LIBEXECDIR}) +install(PROGRAMS plasma-sourceenv.sh DESTINATION ${KDE_INSTALL_LIBEXECDIR}) diff --git a/startkde/config-startplasma.h.cmake b/startkde/config-startplasma.h.cmake new file mode 100644 index 000000000..fc8447056 --- /dev/null +++ b/startkde/config-startplasma.h.cmake @@ -0,0 +1,10 @@ +#ifndef CONFIG_STARTPLASMA_H +#define CONFIG_STARTPLASMA_H + +#define CMAKE_INSTALL_FULL_BINDIR "@CMAKE_INSTALL_FULL_BINDIR@" +#define KDE_INSTALL_FULL_DATAROOTDIR "@KDE_INSTALL_FULL_DATAROOTDIR@" +#define CMAKE_INSTALL_FULL_LIBEXECDIR "@CMAKE_INSTALL_FULL_LIBEXECDIR@" +#define CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "@CMAKE_INSTALL_FULL_LIBEXECDIR_KF5@" +#define KWIN_WAYLAND_BIN_PATH "@KWIN_WAYLAND_BIN_PATH@" + +#endif diff --git a/startkde/kcheckrunning/CMakeLists.txt b/startkde/kcheckrunning/CMakeLists.txt deleted file mode 100644 index 9f2a073af..000000000 --- a/startkde/kcheckrunning/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -set(kcheckrunning_SRCS - kcheckrunning.cpp) - -add_executable( kcheckrunning ${kcheckrunning_SRCS}) - -target_link_libraries(kcheckrunning ${X11_LIBRARIES}) -target_include_directories(kcheckrunning PRIVATE ${X11_X11_INCLUDE_PATH}) - -install(TARGETS kcheckrunning ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/startkde/kcheckrunning/kcheckrunning.cpp b/startkde/kcheckrunning/kcheckrunning.cpp index cb61c12a4..85a72e205 100644 --- a/startkde/kcheckrunning/kcheckrunning.cpp +++ b/startkde/kcheckrunning/kcheckrunning.cpp @@ -1,33 +1,34 @@ /* This file is part of the KDE project Copyright (C) 2005 Lubos Lunak This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include +#include "kcheckrunning.h" /* Return 0 when KDE is running, 1 when KDE is not running but it is possible to connect to X, 2 when it's not possible to connect to X. */ -int main() - { +CheckRunningState kCheckRunning() +{ Display* dpy = XOpenDisplay( nullptr ); if( dpy == nullptr ) - return 2; + return NoX11; Atom atom = XInternAtom( dpy, "_KDE_RUNNING", False ); - return XGetSelectionOwner( dpy, atom ) != None ? 0 : 1; - } + return XGetSelectionOwner( dpy, atom ) != None ? PlasmaRunning : NoPlasmaRunning; +} diff --git a/startkde/kcheckrunning/kcheckrunning.cpp b/startkde/kcheckrunning/kcheckrunning.h similarity index 64% copy from startkde/kcheckrunning/kcheckrunning.cpp copy to startkde/kcheckrunning/kcheckrunning.h index cb61c12a4..9318771c3 100644 --- a/startkde/kcheckrunning/kcheckrunning.cpp +++ b/startkde/kcheckrunning/kcheckrunning.h @@ -1,33 +1,31 @@ /* This file is part of the KDE project - Copyright (C) 2005 Lubos Lunak + Copyright (C) 2019 Aleix Pol Gonzalez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include +#ifndef KCHECKRUNNING_H +#define KCHECKRUNNING_H -/* - Return 0 when KDE is running, 1 when KDE is not running but it is possible - to connect to X, 2 when it's not possible to connect to X. -*/ -int main() - { - Display* dpy = XOpenDisplay( nullptr ); - if( dpy == nullptr ) - return 2; - Atom atom = XInternAtom( dpy, "_KDE_RUNNING", False ); - return XGetSelectionOwner( dpy, atom ) != None ? 0 : 1; - } +enum CheckRunningState { + PlasmaRunning, + NoPlasmaRunning, + NoX11 +}; + +CheckRunningState kCheckRunning(); + +#endif diff --git a/startkde/kstartupconfig/CMakeLists.txt b/startkde/kstartupconfig/CMakeLists.txt deleted file mode 100644 index 80bafbbec..000000000 --- a/startkde/kstartupconfig/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -########### kstartupconfig ############### - -set(kstartupconfig_SRCS kstartupconfig.cpp ) - -add_executable(kstartupconfig5 ${kstartupconfig_SRCS}) - -install(TARGETS kstartupconfig5 ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ) - -########### kdostartupconfig ############### - -set(kdostartupconfig_SRCS kdostartupconfig.cpp ) - -add_executable(kdostartupconfig5 ${kdostartupconfig_SRCS}) - -target_link_libraries(kdostartupconfig5 - Qt5::Core - KF5::ConfigCore - KF5::CoreAddons - ${LIBBSD_LIBRARIES}) - -install(TARGETS kdostartupconfig5 ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ) - diff --git a/startkde/kstartupconfig/kdostartupconfig.cpp b/startkde/kstartupconfig/kdostartupconfig.cpp deleted file mode 100644 index 4016418bd..000000000 --- a/startkde/kstartupconfig/kdostartupconfig.cpp +++ /dev/null @@ -1,155 +0,0 @@ -/**************************************************************************** - - Copyright (C) 2005 Lubos Lunak - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. - -****************************************************************************/ - -#undef QT_NO_CAST_ASCII - -// See description in kstartupconfig.cpp . -#include -#include -#include -#include - -#include -#include -#include - -static QString get_entry( QString* ll ) - { - QString& l = *ll; - l = l.trimmed(); - if( l.isEmpty()) - return QString(); - QString ret; - if( l[ 0 ] == '\'' ) - { - int pos = 1; - while( pos < l.length() && l[ pos ] != '\'' ) - ret += l[ pos++ ]; - if( pos >= l.length()) - { - *ll = QString(); - return QString(); - } - *ll = l.mid( pos + 1 ); - return ret; - } - int pos = 0; - while( pos < l.length() && l[ pos ] != ' ' ) - ret += l[ pos++ ]; - *ll = l.mid( pos ); - return ret; - } - -int main( int argc, char **argv ) - { - Q_UNUSED(argc); - Q_UNUSED(argv); - QString path = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); - QDir().mkdir(path); - - QString keysname = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QStringLiteral("startupconfigkeys")); - QFile keys( keysname ); - if( !keys.open( QIODevice::ReadOnly )) - return 3; - QFile f1(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/startupconfig")); - if( !f1.open( QIODevice::WriteOnly )) - return 4; - QFile f2(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/startupconfigfiles")); - if( !f2.open( QIODevice::WriteOnly )) - return 5; - QTextStream startupconfig( &f1 ); - QTextStream startupconfigfiles( &f2 ); - startupconfig << "#! /bin/sh\n"; - for(;;) - { - QString line = QString::fromLocal8Bit(keys.readLine()).trimmed(); - if( line.isEmpty()) - break; - - QString tmp = line; - QString file, group, key, def; - file = get_entry( &tmp ); - group = get_entry( &tmp ); - key = get_entry( &tmp ); - def = get_entry( &tmp ); - if( file.isEmpty() || group.isEmpty()) - return 6; - if( group.startsWith( '[' ) && group.endsWith( ']' ) ) - { // whole config group - KConfig cfg( file ); - group = group.mid( 1, group.length() - 2 ); - KConfigGroup cg(&cfg, group); - QMap< QString, QString > entries = cg.entryMap( ); - startupconfig << "# " << line << "\n"; - for( QMap< QString, QString >::ConstIterator it = entries.constBegin(); - it != entries.constEnd(); - ++it ) - { - QString key = it.key(); - QString value = *it; - startupconfig << file.replace( ' ', '_' ).toLower() - << "_" << group.replace( ' ', '_' ).toLower() - << "_" << key.replace( ' ', '_' ).toLower() - << "=" << KShell::quoteArg( value ) << "\n"; - } - } - else - { // a single key - if( key.isEmpty()) - return 7; - KConfig cfg( file ); - KConfigGroup cg(&cfg, group ); - QString value = cg.readEntry( key, def ); - startupconfig << "# " << line << "\n"; - startupconfig << file.replace( ' ', '_' ).toLower() - << "_" << group.replace( ' ', '_' ).toLower() - << "_" << key.replace( ' ', '_' ).toLower() - << "=" << KShell::quoteArg( value ) << "\n"; - } - startupconfigfiles << line << endl; - - //we want a list of ~/.config + /usr/share/config (on a normal setup). - //These files may not exist yet as the latter is used by kconf_update - QStringList configDirs = QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation); - foreach (const QString &location, QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation)) { - //some people accidentally have empty prefixes in their XDG_DATA_DIRS, strip those out - if (location.isEmpty()) { - continue; - } - configDirs << (location + "/config"); - } - - foreach (const QString &configDir, configDirs) { - const QString cfg = configDir + '/' + file; - if (QFile::exists(cfg)) - startupconfigfiles << cfg << "\n"; - else - startupconfigfiles << "!" << cfg << "\n"; - } - - startupconfigfiles << "*\n"; - } - - return 0; -} diff --git a/startkde/kstartupconfig/kstartupconfig.cpp b/startkde/kstartupconfig/kstartupconfig.cpp deleted file mode 100644 index 493218ea4..000000000 --- a/startkde/kstartupconfig/kstartupconfig.cpp +++ /dev/null @@ -1,151 +0,0 @@ -/**************************************************************************** - - Copyright (C) 2005 Lubos Lunak - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. - -****************************************************************************/ - -/* - -This utility helps to have some configuration options available in startkde -without the need to launch anything linked to KDE libraries (which may need -some time to load). - -The configuration options are written to $KDEHOME/share/config/startupconfigkeys, -one option per line, as . It is possible to -use ' for quoting multiword entries. Values of these options will be written -to $KDEHOME/share/config/startupconfig as a shell script that will set -the values to shell variables, named __ (all spaces replaced -by underscores, everything lowercase). So e.g. line -"ksplashrc KSplash Theme Default" may result in "ksplashrc_ksplash_theme=Default". - -In order to real a whole group it is possible to use <[group]>, e.g. -"ksplashrc [KSplash]", which will set shell variables for all keys in the group. -It is not possible to specify default values, but since the configuration options -are processed in the order they are specified this can be solved by first -specifying a group and then all the entries that need default values. - -When a kconf_update script is used to update such option, kstartupconfig is run -before kconf_update and therefore cannot see the change in time. To avoid this -problem, together with the kconf_update script also the matching global config -file should be updated (any change, kstartupconfig will see the timestamp change). - -Note that the kdeglobals config file is not used as a depedendency for other config -files. - -Since the checking is timestamp-based, config files that are frequently updated -should not be used. - -Kstartupconfig works by storing every line from startupconfigkeys in file startupconfigfiles -followed by paths of all files that are relevant to the option. Non-existent files -have '!' prepended (for the case they'll be later created), the list of files is -terminated by line containing '*'. If the timestamps of all relevant files are older -than the timestamp of the startupconfigfile file, there's no need to update anything. -Otherwise kdostartupconfig is launched to create or update all the necessary files -(which already requires loading KDE libraries, but this case should be rare). - -*/ - -#include -#include -#include -#include -#include -#include -#include -#include - -int main() -{ - time_t config_time; - FILE* config; - FILE* keys; - struct stat st; - std::string kdehome; - std::string filename; - - if (getenv( "XDG_CONFIG_HOME" )) { - kdehome = getenv("XDG_CONFIG_HOME"); - } else { - kdehome = std::string(getenv("HOME")) + "/.config"; - } - filename = kdehome + "/startupconfig"; - if (access(filename.c_str(), R_OK) != 0) - goto doit; - filename = kdehome + "/startupconfigfiles"; - if (stat(filename.c_str(), &st) != 0) - goto doit; - config_time = st.st_mtime; - config = fopen(filename.c_str(), "r"); - if( config == nullptr ) - goto doit; - filename = kdehome + "/startupconfigkeys"; - keys = fopen(filename.c_str(), "r"); - if( keys == nullptr ) - { - fclose(config); - return 2; - } - for(;;) - { - char* nl; - char keyline[ 1024 ]; - char line[ 1024 ]; - - if( fgets( keyline, 1023, keys ) == nullptr ) - return 0; - if( (nl = strchr( keyline, '\n' )) ) - *nl = '\0'; - if( fgets( line, 1023, config ) == nullptr ) - break; - if( (nl = strchr( line, '\n' )) ) - *nl = '\0'; - if( strcmp( keyline, line ) != 0 ) - break; - for(;;) - { - if( fgets( line, 1023, config ) == nullptr ) - goto doit2; - if( (nl = strchr( line, '\n' )) ) - *nl = '\0'; - if( *line == '\0' ) - goto doit2; - if( *line == '*' ) - break; - if( *line == '!' ) - { - if( access( line + 1, R_OK ) == 0 ) - goto doit2; /* file now exists -> update */ - } - else - { - if( stat( line, &st ) != 0 ) - goto doit2; - if( st.st_mtime > config_time ) - goto doit2; - } - } - } - doit2: - fclose( keys ); - fclose( config ); - doit: - return system( "kdostartupconfig5" ); - } diff --git a/startkde/plasma-sourceenv.sh b/startkde/plasma-sourceenv.sh new file mode 100644 index 000000000..70c3b6487 --- /dev/null +++ b/startkde/plasma-sourceenv.sh @@ -0,0 +1,6 @@ +for i in $@ +do + . $i >/dev/null +done + +env diff --git a/startkde/startkde.cmake b/startkde/startkde.cmake deleted file mode 100644 index bc05e9f1e..000000000 --- a/startkde/startkde.cmake +++ /dev/null @@ -1,310 +0,0 @@ -#!/bin/sh -# -# DEFAULT Plasma STARTUP SCRIPT ( @PROJECT_VERSION@ ) -# - -# When the X server dies we get a HUP signal from xinit. We must ignore it -# because we still need to do some cleanup. -trap 'echo GOT SIGHUP' HUP - -# Check if a Plasma session already is running and whether it's possible to connect to X -kcheckrunning -kcheckrunning_result=$? -if test $kcheckrunning_result -eq 0 ; then - echo "Plasma seems to be already running on this display." - xmessage -geometry 500x100 "Plasma seems to be already running on this display." > /dev/null 2>/dev/null - exit 1 -elif test $kcheckrunning_result -eq 2 ; then - echo "\$DISPLAY is not set or cannot connect to the X server." - exit 1 -fi - -# Boot sequence: -# -# kdeinit is used to fork off processes which improves memory usage -# and startup time. -# -# * kdeinit starts klauncher first. -# * Then kded is started. kded is responsible for keeping the sycoca -# database up to date. When an up to date database is present it goes -# into the background and the startup continues. -# * Then kdeinit starts kcminit. kcminit performs initialisation of -# certain devices according to the user's settings -# -# * Then ksmserver is started which takes control of the rest of the startup sequence - -if [ ${XDG_CONFIG_HOME} ]; then - configDir=$XDG_CONFIG_HOME; -else - configDir=${HOME}/.config; #this is the default, http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html -fi -sysConfigDirs=${XDG_CONFIG_DIRS:-/etc/xdg} - -# We need to create config folder so we can write startupconfigkeys -mkdir -p $configDir - -#This is basically setting defaults so we can use them with kstartupconfig5 -cat >$configDir/startupconfigkeys <$plasmalocalerc </plasma-workspace/env/*.sh -# (where correspond to the system and user's configuration -# directory. -# -# This is where you can define environment variables that will be available to -# all KDE programs, so this is where you can run agents using e.g. eval `ssh-agent` -# or eval `gpg-agent --daemon`. -# Note: if you do that, you should also put "ssh-agent -k" as a shutdown script -# -# (see end of this file). -# For anything else (that doesn't set env vars, or that needs a window manager), -# better use the Autostart folder. - -scriptpath=`echo "$configDir:$sysConfigDirs" | tr ':' '\n'` - -for prefix in `echo $scriptpath`; do - for file in "$prefix"/plasma-workspace/env/*.sh; do - test -r "$file" && . "$file" || true - done -done - -# Set a left cursor instead of the standard X11 "X" cursor, since I've heard -# from some users that they're confused and don't know what to do. This is -# especially necessary on slow machines, where starting KDE takes one or two -# minutes until anything appears on the screen. -# -# If the user has overwritten fonts, the cursor font may be different now -# so don't move this up. -# -xsetroot -cursor_name left_ptr - -# Get Ghostscript to look into user's KDE fonts dir for additional Fontmap -usr_fdir=$HOME/.fonts -if test -n "$GS_LIB" ; then - GS_LIB=$usr_fdir:$GS_LIB - export GS_LIB -else - GS_LIB=$usr_fdir - export GS_LIB -fi - -echo 'startkde: Starting up...' 1>&2 - -# Make sure that the KDE prefix is first in XDG_DATA_DIRS and that it's set at all. -# The spec allows XDG_DATA_DIRS to be not set, but X session startup scripts tend -# to set it to a list of paths *not* including the KDE prefix if it's not /usr or -# /usr/local. -if test -z "$XDG_DATA_DIRS"; then - XDG_DATA_DIRS="@KDE_INSTALL_FULL_DATAROOTDIR@:/usr/share:/usr/local/share" -fi -export XDG_DATA_DIRS - -# Mark that full KDE session is running (e.g. Konqueror preloading works only -# with full KDE running). The KDE_FULL_SESSION property can be detected by -# any X client connected to the same X session, even if not launched -# directly from the KDE session but e.g. using "ssh -X", kdesu. $KDE_FULL_SESSION -# however guarantees that the application is launched in the same environment -# like the KDE session and that e.g. KDE utilities/libraries are available. -# KDE_FULL_SESSION property is also only available since KDE 3.5.5. -# The matching tests are: -# For $KDE_FULL_SESSION: -# if test -n "$KDE_FULL_SESSION"; then ... whatever -# For KDE_FULL_SESSION property: -# xprop -root | grep "^KDE_FULL_SESSION" >/dev/null 2>/dev/null -# if test $? -eq 0; then ... whatever -# -# Additionally there is (since KDE 3.5.7) $KDE_SESSION_UID with the uid -# of the user running the KDE session. It should be rarely needed (e.g. -# after sudo to prevent desktop-wide functionality in the new user's kded). -# -# Since KDE4 there is also KDE_SESSION_VERSION, containing the major version number. -# Note that this didn't exist in KDE3, which can be detected by its absense and -# the presence of KDE_FULL_SESSION. -# -KDE_FULL_SESSION=true -export KDE_FULL_SESSION -xprop -root -f KDE_FULL_SESSION 8t -set KDE_FULL_SESSION true - -KDE_SESSION_VERSION=5 -export KDE_SESSION_VERSION -xprop -root -f KDE_SESSION_VERSION 32c -set KDE_SESSION_VERSION 5 - -KDE_SESSION_UID=`id -ru` -export KDE_SESSION_UID - -XDG_CURRENT_DESKTOP=KDE -export XDG_CURRENT_DESKTOP - -# At this point all environment variables are set, let's send it to the DBus session server to update the activation environment -if which dbus-update-activation-environment >/dev/null 2>/dev/null ; then - dbus-update-activation-environment --systemd --all -else - @CMAKE_INSTALL_FULL_LIBEXECDIR@/ksyncdbusenv -fi -if test $? -ne 0; then - # Startup error - echo 'startkde: Could not sync environment to dbus.' 1>&2 - test -n "$ksplash_pid" && kill "$ksplash_pid" 2>/dev/null - xmessage -geometry 500x100 "Could not sync environment to dbus." - exit 1 -fi - -# We set LD_BIND_NOW to increase the efficiency of kdeinit. -# kdeinit unsets this variable before loading applications. -LD_BIND_NOW=true @CMAKE_INSTALL_FULL_LIBEXECDIR_KF5@/start_kdeinit_wrapper --kded +kcminit_startup -if test $? -ne 0; then - # Startup error - echo 'startkde: Could not start kdeinit5. Check your installation.' 1>&2 - test -n "$ksplash_pid" && kill "$ksplash_pid" 2>/dev/null - xmessage -geometry 500x100 "Could not start kdeinit5. Check your installation." - exit 1 -fi - -qdbus org.kde.KSplash /KSplash org.kde.KSplash.setStage kinit & - -# finally, give the session control to the session manager -# see kdebase/ksmserver for the description of the rest of the startup sequence -# if the KDEWM environment variable has been set, then it will be used as KDE's -# window manager instead of kwin. -# if KDEWM is not set, ksmserver will ensure kwin is started. -# kwrapper5 is used to reduce startup time and memory usage -# kwrapper5 does not return useful error codes such as the exit code of ksmserver. -# We only check for 255 which means that the ksmserver process could not be -# started, any problems thereafter, e.g. ksmserver failing to initialize, -# will remain undetected. -test -n "$KDEWM" && KDEWM="--windowmanager $KDEWM" -# If the session should be locked from the start (locked autologin), -# lock now and do the rest of the KDE startup underneath the locker. -KSMSERVEROPTIONS="" -test -n "$dl" && KSMSERVEROPTIONS=" --lockscreen" -kwrapper5 @CMAKE_INSTALL_FULL_BINDIR@/ksmserver $KDEWM $KSMSERVEROPTIONS -if test $? -eq 255; then - # Startup error - echo 'startkde: Could not start ksmserver. Check your installation.' 1>&2 - test -n "$ksplash_pid" && kill "$ksplash_pid" 2>/dev/null - xmessage -geometry 500x100 "Could not start ksmserver. Check your installation." -fi - -#Anything after here is logout -#It is not called after shutdown/restart - -wait_drkonqi=`kreadconfig5 --file startkderc --group WaitForDrKonqi --key Enabled --default true` - -if test x"$wait_drkonqi"x = x"true"x ; then - # wait for remaining drkonqi instances with timeout (in seconds) - wait_drkonqi_timeout=`kreadconfig5 --file startkderc --group WaitForDrKonqi --key Timeout --default 900` - wait_drkonqi_counter=0 - while qdbus | grep "^[^w]*org.kde.drkonqi" > /dev/null ; do - sleep 5 - wait_drkonqi_counter=$((wait_drkonqi_counter+5)) - if test "$wait_drkonqi_counter" -ge "$wait_drkonqi_timeout" ; then - # ask remaining drkonqis to die in a graceful way - qdbus | grep 'org.kde.drkonqi-' | while read address ; do - qdbus "$address" "/MainApplication" "quit" - done - break - fi - done -fi - -echo 'startkde: Shutting down...' 1>&2 -# just in case -test -n "$ksplash_pid" && kill "$ksplash_pid" 2>/dev/null - -# Clean up -kdeinit5_shutdown - -unset KDE_FULL_SESSION -xprop -root -remove KDE_FULL_SESSION -unset KDE_SESSION_VERSION -xprop -root -remove KDE_SESSION_VERSION -unset KDE_SESSION_UID - -echo 'startkde: Done.' 1>&2 diff --git a/startkde/startplasma-wayland.cpp b/startkde/startplasma-wayland.cpp new file mode 100644 index 000000000..5b0e0746d --- /dev/null +++ b/startkde/startplasma-wayland.cpp @@ -0,0 +1,92 @@ +/* This file is part of the KDE project + Copyright (C) 2019 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "startplasma.h" +#include +#include +#include +#include + +int main(int /*argc*/, char** /*argv*/) +{ + createConfigDirectory(); + setupCursor(true); + + { + KConfig fonts(QStringLiteral("kcmfonts")); + KConfigGroup group = fonts.group("General"); + auto dpiSetting = group.readEntry("forceFontDPIWayland", 96); + auto dpi = dpiSetting == 0 ? 96 : dpiSetting; + qputenv("QT_WAYLAND_FORCE_DPI", QByteArray::number(dpi)); + } + + // Query whether org.freedesktop.locale1 is available. If it is, try to + // set XKB_DEFAULT_{MODEL,LAYOUT,VARIANT,OPTIONS} accordingly. + { + const QString locale1Service = QStringLiteral("org.freedesktop.locale1"); + const QString locale1Path = QStringLiteral("/org/freedesktop/locale1"); + QDBusMessage message = QDBusMessage::createMethodCall(locale1Service, + locale1Path, + QStringLiteral("org.freedesktop.DBus.Properties"), + QLatin1String("GetAll")); + message << locale1Service; + QDBusMessage resultMessage = QDBusConnection::systemBus().call(message); + if (resultMessage.type() == QDBusMessage::ReplyMessage) { + QVariantMap result; + QDBusArgument dbusArgument = resultMessage.arguments().at(0).value(); + while (!dbusArgument.atEnd()) { + dbusArgument >> result; + } + + auto queryAndSet = [&](const QByteArray &var, const QString & value) { + const auto r = result.value(value).toString(); + if (!r.isEmpty()) + qputenv(var, r.toUtf8()); + }; + + queryAndSet("X11MODEL", QStringLiteral("X11Model")); + queryAndSet("X11LAYOUT", QStringLiteral("X11Layout")); + queryAndSet("X11VARIANT", QStringLiteral("X11Variant")); + queryAndSet("X11OPTIONS", QStringLiteral("X11Options")); + } else { + qWarning() << "not a reply org.freedesktop.locale1" << resultMessage; + } + } + runEnvironmentScripts(); + + if (!qEnvironmentVariableIsSet("DBUS_SESSION_BUS_ADDRESS")) { + out << "startplasmacompositor: Could not start D-Bus. Can you call qdbus?\n"; + return 1; + } + setupPlasmaEnvironment(); + qputenv("XDG_SESSION_TYPE", "wayland"); + + if (!syncDBusEnvironment()) { + out << "Could not sync environment to dbus.\n"; + return 1; + } + + runSync(QStringLiteral(KWIN_WAYLAND_BIN_PATH), { QStringLiteral("--xwayland"), QStringLiteral("--libinput"), QStringLiteral("--exit-with-session=" CMAKE_INSTALL_FULL_LIBEXECDIR "/startplasma-waylandsession") }); + + out << "startplasmacompositor: Shutting down...\n"; + cleanupPlasmaEnvironment(); + out << "startplasmacompositor: Done.\n"; + + return 0; +} diff --git a/startkde/startplasma-waylandsession.cpp b/startkde/startplasma-waylandsession.cpp new file mode 100644 index 000000000..c164bc4ba --- /dev/null +++ b/startkde/startplasma-waylandsession.cpp @@ -0,0 +1,76 @@ +/* This file is part of the KDE project + Copyright (C) 2019 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "startplasma.h" + +int main(int /*argc*/, char** /*argv*/) +{ + // Boot sequence: + // + // kdeinit is used to fork off processes which improves memory usage + // and startup time. + // + // * kdeinit starts klauncher first. + // * Then kded is started. kded is responsible for keeping the sycoca + // database up to date. When an up to date database is present it goes + // into the background and the startup continues. + // * Then kdeinit starts kcminit. kcminit performs initialisation of + // certain devices according to the user's settings + // + // * Then ksmserver is started which takes control of the rest of the startup sequence + + runStartupConfig(); + + setupFontDpi(); + + QScopedPointer ksplash(setupKSplash()); + qputenv("PLASMA_USE_QT_SCALING", "1"); + + out << "startplasma-waylandsession: Starting up..."; + + if (qEnvironmentVariableIsSet("DISPLAY")) { + setupX11(); + } else { + qWarning() << "running kwin without Xwayland support"; + } + setupGSLib(); + + if (!syncDBusEnvironment()) { + out << "Could not sync environment to dbus.\n"; + return 2; + } + + if (!startKDEInit()) + return 3; + + if (!startKSMServer()) + return 4; + + // Anything after here is logout + // It is not called after shutdown/restart + waitForKonqi(); + out << "startplasma-waylandsession: Shutting down...\n"; + + runSync(QStringLiteral("kdeinit5_shutdown"), {}); + + cleanupX11(); + out << "startplasma-waylandsession: Done.\n"; + + return 0; +} diff --git a/startkde/startplasma-x11.cpp b/startkde/startplasma-x11.cpp new file mode 100644 index 000000000..f5e08bcbf --- /dev/null +++ b/startkde/startplasma-x11.cpp @@ -0,0 +1,120 @@ +/* This file is part of the KDE project + Copyright (C) 2019 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "startplasma.h" + +#include +#include +#include +#include + +void sighupHandler(int) +{ + out << "GOT SIGHUP\n"; +} + +int main(int /*argc*/, char** /*argv*/) +{ + // When the X server dies we get a HUP signal from xinit. We must ignore it + // because we still need to do some cleanup. + signal(SIGHUP, sighupHandler); + + // Boot sequence: + // + // kdeinit is used to fork off processes which improves memory usage + // and startup time. + // + // * kdeinit starts klauncher first. + // * Then kded is started. kded is responsible for keeping the sycoca + // database up to date. When an up to date database is present it goes + // into the background and the startup continues. + // * Then kdeinit starts kcminit. kcminit performs initialisation of + // certain devices according to the user's settings + // + // * Then ksmserver is started which takes control of the rest of the startup sequence + + // Check if a Plasma session already is running and whether it's possible to connect to X + switch (kCheckRunning()) { + case NoX11: + out << "$DISPLAY is not set or cannot connect to the X server.\n"; + return 1; + case PlasmaRunning: + messageBox(QStringLiteral("Plasma seems to be already running on this display.\n")); + return 1; + case NoPlasmaRunning: + break; + } + + createConfigDirectory(); + runStartupConfig(); + + //Do not sync any of this section with the wayland versions as there scale factors are + //sent properly over wl_output + + { + KConfig cfg(QStringLiteral("kdeglobals")); + + const auto screenScaleFactors = cfg.group("KScreen").readEntry("ScreenScaleFactors", QByteArray()); + if (!screenScaleFactors.isEmpty()) { + qputenv("QT_SCREEN_SCALE_FACTORS", screenScaleFactors); + if (screenScaleFactors == "2" || screenScaleFactors == "3") { + qputenv("GDK_SCALE", screenScaleFactors); + qputenv("GDK_DPI_SCALE", QByteArray::number(1./screenScaleFactors.toDouble(), 'g', 3)); + } + } + } + + setupCursor(false); + setupFontDpi(); + QScopedPointer ksplash(setupKSplash()); + + runEnvironmentScripts(); + + out << "startkde: Starting up...\n"; + + setupPlasmaEnvironment(); + setupX11(); + + if (!syncDBusEnvironment()) { + // Startup error + messageBox(QStringLiteral("Could not sync environment to dbus.\n")); + return 1; + } + + if (!startKDEInit()) + return 1; + + if (!startKSMServer()) + return 1; + + // Anything after here is logout + // It is not called after shutdown/restart + waitForKonqi(); + + out << "startkde: Shutting down...\n"; + + runSync(QStringLiteral("kdeinit5_shutdown"), {}); + + cleanupPlasmaEnvironment(); + cleanupX11(); + + out << "startkde: Done.\n"; + + return 0; +} diff --git a/startkde/startplasma.cmake b/startkde/startplasma.cmake deleted file mode 100644 index 1fe41c59e..000000000 --- a/startkde/startplasma.cmake +++ /dev/null @@ -1,160 +0,0 @@ -#!/bin/sh -# -# DEFAULT Plasma STARTUP SCRIPT ( @PROJECT_VERSION@ ) -# - -# Boot sequence: -# -# kdeinit is used to fork off processes which improves memory usage -# and startup time. -# -# * kdeinit starts klauncher first. -# * Then kded is started. kded is responsible for keeping the sycoca -# database up to date. When an up to date database is present it goes -# into the background and the startup continues. -# * Then kdeinit starts kcminit. kcminit performs initialisation of -# certain devices according to the user's settings -# -# * Then ksmserver is started which takes control of the rest of the startup sequence - -# We need to create config folder so we can write startupconfigkeys -if [ ${XDG_CONFIG_HOME} ]; then - configDir=$XDG_CONFIG_HOME; -else - configDir=${HOME}/.config; #this is the default, http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html -fi - -[ -r $configDir/startupconfig ] && . $configDir/startupconfig - -xrdb -quiet -merge -nocpp <&2 - -# export our session variables to the Xwayland server -xprop -root -f KDE_FULL_SESSION 8t -set KDE_FULL_SESSION true -xprop -root -f KDE_SESSION_VERSION 32c -set KDE_SESSION_VERSION 5 - -# At this point all environment variables are set, let's send it to the DBus session server to update the activation environment -if which dbus-update-activation-environment >/dev/null 2>/dev/null ; then - dbus-update-activation-environment --systemd --all -else - @CMAKE_INSTALL_FULL_LIBEXECDIR@/ksyncdbusenv -fi -if test $? -ne 0; then - # Startup error - echo 'startplasma: Could not sync environment to dbus.' 1>&2 - exit 1 -fi - -# We set LD_BIND_NOW to increase the efficiency of kdeinit. -# kdeinit unsets this variable before loading applications. -LD_BIND_NOW=true @CMAKE_INSTALL_FULL_LIBEXECDIR_KF5@/start_kdeinit_wrapper --kded +kcminit_startup -if test $? -ne 0; then - # Startup error - echo 'startplasma: Could not start kdeinit5. Check your installation.' 1>&2 - test -n "$ksplash_pid" && kill "$ksplash_pid" 2>/dev/null - xmessage -geometry 500x100 "Could not start kdeinit5. Check your installation." - exit 1 -fi - -qdbus org.kde.KSplash /KSplash org.kde.KSplash.setStage kinit & - -# finally, give the session control to the session manager -# see kdebase/ksmserver for the description of the rest of the startup sequence -# if the KDEWM environment variable has been set, then it will be used as KDE's -# window manager instead of kwin. -# if KDEWM is not set, ksmserver will ensure kwin is started. -# kwrapper5 is used to reduce startup time and memory usage -# kwrapper5 does not return useful error codes such as the exit code of ksmserver. -# We only check for 255 which means that the ksmserver process could not be -# started, any problems thereafter, e.g. ksmserver failing to initialize, -# will remain undetected. -# If the session should be locked from the start (locked autologin), -# lock now and do the rest of the KDE startup underneath the locker. -KSMSERVEROPTIONS=" --no-lockscreen" -kwrapper5 @CMAKE_INSTALL_FULL_BINDIR@/ksmserver $KDEWM $KSMSERVEROPTIONS -if test $? -eq 255; then - # Startup error - echo 'startplasma: Could not start ksmserver. Check your installation.' 1>&2 - test -n "$ksplash_pid" && kill "$ksplash_pid" 2>/dev/null - xmessage -geometry 500x100 "Could not start ksmserver. Check your installation." -fi - -#Anything after here is logout -#It is not called after shutdown/restart - -wait_drkonqi=`kreadconfig5 --file startkderc --group WaitForDrKonqi --key Enabled --default true` - -if test x"$wait_drkonqi"x = x"true"x ; then - # wait for remaining drkonqi instances with timeout (in seconds) - wait_drkonqi_timeout=`kreadconfig5 --file startkderc --group WaitForDrKonqi --key Timeout --default 900` - wait_drkonqi_counter=0 - while qdbus | grep "^[^w]*org.kde.drkonqi" > /dev/null ; do - sleep 5 - wait_drkonqi_counter=$((wait_drkonqi_counter+5)) - if test "$wait_drkonqi_counter" -ge "$wait_drkonqi_timeout" ; then - # ask remaining drkonqis to die in a graceful way - qdbus | grep 'org.kde.drkonqi-' | while read address ; do - qdbus "$address" "/MainApplication" "quit" - done - break - fi - done -fi - -echo 'startplasma: Shutting down...' 1>&2 -# just in case -test -n "$ksplash_pid" && kill "$ksplash_pid" 2>/dev/null - -# Clean up -kdeinit5_shutdown - -unset KDE_FULL_SESSION -xprop -root -remove KDE_FULL_SESSION -unset KDE_SESSION_VERSION -xprop -root -remove KDE_SESSION_VERSION -unset KDE_SESSION_UID - -echo 'startplasma: Done.' 1>&2 diff --git a/startkde/startplasma.cpp b/startkde/startplasma.cpp new file mode 100644 index 000000000..cc5cd2bc4 --- /dev/null +++ b/startkde/startplasma.cpp @@ -0,0 +1,395 @@ +/* This file is part of the KDE project + Copyright (C) 2019 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "startplasma.h" + +QTextStream out(stderr); + +void messageBox(const QString &text) +{ + out << text; + runSync(QStringLiteral("xmessage"), {QStringLiteral("-geometry"), QStringLiteral("500x100"), text}); +} + +QStringList allServices(const QLatin1String& prefix) +{ + QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface(); + const QStringList services = bus->registeredServiceNames(); + QMap servicesWithAliases; + + for (const QString &serviceName : services) { + QDBusReply reply = bus->serviceOwner(serviceName); + QString owner = reply; + if (owner.isEmpty()) + owner = serviceName; + servicesWithAliases[owner].append(serviceName); + } + + QStringList names; + for (auto it = servicesWithAliases.constBegin(); it != servicesWithAliases.constEnd(); ++it) { + if (it.value().startsWith(prefix)) + names << it.value(); + } + names.removeDuplicates(); + names.sort(); + return names; +} + +int runSync(const QString& program, const QStringList &args, const QStringList &env) +{ + QProcess p; + if (!env.isEmpty()) + p.setEnvironment(QProcess::systemEnvironment() << env); + p.setProcessChannelMode(QProcess::ForwardedChannels); + p.start(program, args); +// qDebug() << "started..." << program << args; + p.waitForFinished(-1); + if (p.exitCode()) { + qWarning() << program << args << "exited with code" << p.exitCode(); + } + return p.exitCode(); +} + +void sourceFiles(const QStringList &files) +{ + QStringList filteredFiles; + std::copy_if(files.begin(), files.end(), std::back_inserter(filteredFiles), [](const QString& i){ return QFileInfo(i).isReadable(); } ); + + if (filteredFiles.isEmpty()) + return; + + filteredFiles.prepend(QStringLiteral(CMAKE_INSTALL_FULL_LIBEXECDIR "/plasma-sourceenv.sh")); + + QProcess p; + p.start(QStringLiteral("/bin/sh"), filteredFiles); + p.waitForFinished(-1); + + const auto fullEnv = p.readAllStandardOutput(); + auto envs = fullEnv.split('\n'); + + for (auto &env: envs) { + if (env.startsWith("_=") || env.startsWith("SHLVL")) + continue; + + const int idx = env.indexOf('='); + if (Q_UNLIKELY(idx <= 0)) + continue; + + if (qgetenv(env.left(idx)) != env.mid(idx+1)) { +// qDebug() << "setting..." << env.left(idx) << env.mid(idx+1) << "was" << qgetenv(env.left(idx)); + qputenv(env.left(idx), env.mid(idx+1)); + } + } +} + +void createConfigDirectory() +{ + const QString configDir = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); + if (!QDir().mkpath(configDir)) + out << "Could not create config directory XDG_CONFIG_HOME: " << configDir << '\n'; +} + +void runStartupConfig() +{ + const QString configDir = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); + + const QString localerc(configDir + QLatin1String("/plasma-localerc")); + if (!QFile::exists(localerc)) { + QFile f(localerc); + f.open(QFile::WriteOnly); + f.write("[Formats]\n" + "LANG=" + qgetenv("LANG") + '\n'); + } + + //export LC_* variables set by kcmshell5 formats into environment + //so it can be picked up by QLocale and friends. + sourceFiles({configDir + QStringLiteral("/plasma-locale-settings.sh")}); +} + +void setupCursor(bool wayland) +{ + const KConfig cfg(QStringLiteral("kcminputrc")); + const KConfigGroup inputCfg = cfg.group("Mouse"); + + const auto kcminputrc_mouse_cursorsize = inputCfg.readEntry("cursorSize", QString()); + const auto kcminputrc_mouse_cursortheme = inputCfg.readEntry("cursorTheme", QStringLiteral("breeze_cursors")); + if (!kcminputrc_mouse_cursortheme.isEmpty() || !kcminputrc_mouse_cursorsize.isEmpty()) { +#ifdef XCURSOR_PATH + QByteArray path(XCURSOR_PATH); + path.replace("$XCURSOR_PATH", qgetenv("XCURSOR_PATH")); + qputenv("XCURSOR_PATH", path); +#endif + } + + //TODO: consider linking directly + const int applyMouseStatus = wayland ? 0 : runSync(QStringLiteral("kapplymousetheme"), { QStringLiteral("kcminputrc_mouse_cursortheme"), QStringLiteral("kcminputrc_mouse_cursorsize") }); + if (applyMouseStatus == 10) { + qputenv("XCURSOR_THEME", "breeze_cursors"); + } else if (!kcminputrc_mouse_cursortheme.isEmpty()) { + qputenv("XCURSOR_THEME", kcminputrc_mouse_cursortheme.toUtf8()); + } + if (!kcminputrc_mouse_cursorsize.isEmpty()) { + qputenv("XCURSOR_SIZE", kcminputrc_mouse_cursorsize.toUtf8()); + } +} + +// Source scripts found in /plasma-workspace/env/*.sh +// (where correspond to the system and user's configuration +// directory. +// +// This is where you can define environment variables that will be available to +// all KDE programs, so this is where you can run agents using e.g. eval `ssh-agent` +// or eval `gpg-agent --daemon`. +// Note: if you do that, you should also put "ssh-agent -k" as a shutdown script +// +// (see end of this file). +// For anything else (that doesn't set env vars, or that needs a window manager), +// better use the Autostart folder. + +void runEnvironmentScripts() +{ + QStringList scripts; + const auto locations = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("plasma-workspace/env"), QStandardPaths::LocateDirectory); + for (const QString & location : locations) { + QDir dir(location); + const auto dirScripts = dir.entryInfoList({QStringLiteral("*.sh")}); + for (const auto script : dirScripts) { + scripts << script.absoluteFilePath(); + } + } + sourceFiles(scripts); + + // Make sure that the KDE prefix is first in XDG_DATA_DIRS and that it's set at all. + // The spec allows XDG_DATA_DIRS to be not set, but X session startup scripts tend + // to set it to a list of paths *not* including the KDE prefix if it's not /usr or + // /usr/local. + if (!qEnvironmentVariableIsSet("XDG_DATA_DIRS")) { + qputenv("XDG_DATA_DIRS", KDE_INSTALL_FULL_DATAROOTDIR ":/usr/share:/usr/local/share"); + } +} + + +// Mark that full KDE session is running (e.g. Konqueror preloading works only +// with full KDE running). The KDE_FULL_SESSION property can be detected by +// any X client connected to the same X session, even if not launched +// directly from the KDE session but e.g. using "ssh -X", kdesu. $KDE_FULL_SESSION +// however guarantees that the application is launched in the same environment +// like the KDE session and that e.g. KDE utilities/libraries are available. +// KDE_FULL_SESSION property is also only available since KDE 3.5.5. +// The matching tests are: +// For $KDE_FULL_SESSION: +// if test -n "$KDE_FULL_SESSION"; then ... whatever +// For KDE_FULL_SESSION property (on X11): +// xprop -root | grep "^KDE_FULL_SESSION" >/dev/null 2>/dev/null +// if test $? -eq 0; then ... whatever +// +// Additionally there is $KDE_SESSION_UID with the uid +// of the user running the KDE session. It should be rarely needed (e.g. +// after sudo to prevent desktop-wide functionality in the new user's kded). +// +// Since KDE4 there is also KDE_SESSION_VERSION, containing the major version number. +// + +void setupPlasmaEnvironment() +{ + //Manually disable auto scaling because we are scaling above + //otherwise apps that manually opt in for high DPI get auto scaled by the developer AND manually scaled by us + qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "0"); + + qputenv("KDE_FULL_SESSION", "true"); + qputenv("KDE_SESSION_VERSION", "5"); + qputenv("KDE_SESSION_UID", QByteArray::number(getuid())); + qputenv("XDG_CURRENT_DESKTOP", "KDE"); +} + +void setupX11() +{ +// Set a left cursor instead of the standard X11 "X" cursor, since I've heard +// from some users that they're confused and don't know what to do. This is +// especially necessary on slow machines, where starting KDE takes one or two +// minutes until anything appears on the screen. +// +// If the user has overwritten fonts, the cursor font may be different now +// so don't move this up. + + runSync(QStringLiteral("xsetroot"), {QStringLiteral("-cursor_name"), QStringLiteral("left_ptr")}); + runSync(QStringLiteral("xprop"), {QStringLiteral("-root"), QStringLiteral("-f"), QStringLiteral("KDE_FULL_SESSION"), QStringLiteral("8t"), QStringLiteral("-set"), QStringLiteral("KDE_FULL_SESSION"), QStringLiteral("true")}); + runSync(QStringLiteral("xprop"), {QStringLiteral("-root"), QStringLiteral("-f"), QStringLiteral("KDE_SESSION_VERSION"), QStringLiteral("32c"), QStringLiteral("-set"), QStringLiteral("KDE_SESSION_VERSION"), QStringLiteral("5")}); +} + +void cleanupX11() +{ + runSync(QStringLiteral("xprop"), { QStringLiteral("-root"), QStringLiteral("-remove"), QStringLiteral("KDE_FULL_SESSION") }); + runSync(QStringLiteral("xprop"), { QStringLiteral("-root"), QStringLiteral("-remove"), QStringLiteral("KDE_SESSION_VERSION") }); +} + +// TODO: Check if Necessary +void cleanupPlasmaEnvironment() +{ + qunsetenv("KDE_FULL_SESSION"); + qunsetenv("KDE_SESSION_VERSION"); + qunsetenv("KDE_SESSION_UID"); +} + +// kwin_wayland can possibly also start dbus-activated services which need env variables. +// In that case, the update in startplasma might be too late. +bool syncDBusEnvironment() +{ + int exitCode; + // At this point all environment variables are set, let's send it to the DBus session server to update the activation environment + if (!QStandardPaths::findExecutable(QStringLiteral("dbus-update-activation-environment")).isEmpty()) { + exitCode = runSync(QStringLiteral("dbus-update-activation-environment"), { QStringLiteral("--systemd"), QStringLiteral("--all") }); + } else { + exitCode = runSync(QStringLiteral(CMAKE_INSTALL_FULL_LIBEXECDIR "/ksyncdbusenv"), {}); + } + return exitCode == 0; +} + +void setupFontDpi() +{ + KConfig cfg(QStringLiteral("kcmfonts")); + KConfigGroup fontsCfg(&cfg, "General"); + + if (!fontsCfg.hasKey("forceFontDPI")) { + return; + } + + //TODO port to c++? + const QByteArray input = "Xft.dpi: " + QByteArray::number(fontsCfg.readEntry("forceFontDPI", 0)); + QProcess p; + p.start(QStringLiteral("xrdb"), { QStringLiteral("-quiet"), QStringLiteral("-merge"), QStringLiteral("-nocpp") }); + p.setProcessChannelMode(QProcess::ForwardedChannels); + p.write(input); + p.closeWriteChannel(); + p.waitForFinished(-1); +} + +static bool desktopLockedAtStart = false; + +QProcess* setupKSplash() +{ + const auto dlstr = qgetenv("DESKTOP_LOCKED"); + desktopLockedAtStart = dlstr == "true" || dlstr == "1"; + qunsetenv("DESKTOP_LOCKED"); // Don't want it in the environment + + QProcess* p = nullptr; + if (!desktopLockedAtStart) { + const KConfig cfg(QStringLiteral("ksplashrc")); + // the splashscreen and progress indicator + KConfigGroup ksplashCfg = cfg.group("KSplash"); + if (ksplashCfg.readEntry("Engine", QStringLiteral("KSplashQML")) == QLatin1String("KSplashQML")) { + p = new QProcess; + p->start(QStringLiteral("ksplashqml"), { ksplashCfg.readEntry("Theme", QStringLiteral("Breeze")) }); + } + } + return p; +} + + +void setupGSLib() +// Get Ghostscript to look into user's KDE fonts dir for additional Fontmap +{ + const QByteArray usr_fdir = QFile::encodeName(QDir::home().absoluteFilePath(QStringLiteral(".fonts"))); + if (qEnvironmentVariableIsSet("GS_LIB")) { + qputenv("GS_LIB", usr_fdir + ':' + qgetenv("GS_LIB")); + } else { + qputenv("GS_LIB", usr_fdir); + } +} + +bool startKDEInit() +{ + // We set LD_BIND_NOW to increase the efficiency of kdeinit. + // kdeinit unsets this variable before loading applications. + const int exitCode = runSync(QStringLiteral(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "/start_kdeinit_wrapper"), { QStringLiteral("--kded"), QStringLiteral("+kcminit_startup") }, { QStringLiteral("LD_BIND_NOW=true") }); + if (exitCode != 0) { + messageBox(QStringLiteral("startkde: Could not start kdeinit5. Check your installation.")); + return false; + } + + OrgKdeKSplashInterface iface(QStringLiteral("org.kde.KSplash"), QStringLiteral("/KSplash"), QDBusConnection::sessionBus()); + iface.setStage(QStringLiteral("kinit")); + return true; +} + +bool startKSMServer() +{ + // finally, give the session control to the session manager + // see kdebase/ksmserver for the description of the rest of the startup sequence + // if the KDEWM environment variable has been set, then it will be used as KDE's + // window manager instead of kwin. + // if KDEWM is not set, ksmserver will ensure kwin is started. + // kwrapper5 is used to reduce startup time and memory usage + // kwrapper5 does not return useful error codes such as the exit code of ksmserver. + // We only check for 255 which means that the ksmserver process could not be + // started, any problems thereafter, e.g. ksmserver failing to initialize, + // will remain undetected. + // If the session should be locked from the start (locked autologin), + // lock now and do the rest of the KDE startup underneath the locker. + + + QStringList ksmserverOptions = { QStringLiteral(CMAKE_INSTALL_FULL_BINDIR "/ksmserver") }; + if (desktopLockedAtStart) { + ksmserverOptions << QStringLiteral("--lockscreen"); + } + const auto exitCode = runSync(QStringLiteral("kwrapper5"), ksmserverOptions); + + if (exitCode == 255) { + // Startup error + messageBox(QStringLiteral("startkde: Could not start ksmserver. Check your installation.\n")); + return false; + } + return true; +} + +void waitForKonqi() +{ + const KConfig cfg(QStringLiteral("startkderc")); + const KConfigGroup grp = cfg.group("WaitForDrKonqi"); + bool wait_drkonqi = grp.readEntry("Enabled", true); + if (wait_drkonqi) { + // wait for remaining drkonqi instances with timeout (in seconds) + const int wait_drkonqi_timeout = grp.readEntry("Timeout", 900) * 1000; + QElapsedTimer wait_drkonqi_counter; + wait_drkonqi_counter.start(); + QStringList services = allServices(QLatin1String("org.kde.drkonqi-")); + while (!services.isEmpty()) { + sleep(5); + services = allServices(QLatin1String("org.kde.drkonqi-")); + if (wait_drkonqi_counter.elapsed() >= wait_drkonqi_timeout) { + // ask remaining drkonqis to die in a graceful way + for (const auto &service: services) { + QDBusInterface iface(service, QStringLiteral("/MainApplication")); + iface.call(QStringLiteral("quit")); + } + break; + } + } + } +} diff --git a/startkde/startplasma.h b/startkde/startplasma.h new file mode 100644 index 000000000..5fe086593 --- /dev/null +++ b/startkde/startplasma.h @@ -0,0 +1,62 @@ +/* This file is part of the KDE project + Copyright (C) 2019 Aleix Pol Gonzalez + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef STARTPLASMA_H +#define STARTPLASMA_H + +#include "kcheckrunning/kcheckrunning.h" +#include +#include "config-startplasma.h" + +extern QTextStream out; + +QStringList allServices(const QLatin1String& prefix); +int runSync(const QString& program, const QStringList &args, const QStringList &env = {}); +void sourceFiles(const QStringList &files); +void messageBox(const QString &text); + +void createConfigDirectory(); +void runStartupConfig(); +void setupCursor(bool wayland); +void runEnvironmentScripts(); +void setupPlasmaEnvironment(); +void cleanupPlasmaEnvironment(); +void cleanupX11(); +bool syncDBusEnvironment(); +void setupFontDpi(); +QProcess* setupKSplash(); +void setupGSLib(); +void setupX11(); + +bool startKDEInit(); +bool startKSMServer(); + +void waitForKonqi(); + +struct KillBeforeDeleter +{ + static inline void cleanup(QProcess *pointer) + { + if (pointer) + pointer->kill(); + delete pointer; + } +}; + +#endif diff --git a/startkde/startplasmacompositor.cmake b/startkde/startplasmacompositor.cmake deleted file mode 100644 index 785733731..000000000 --- a/startkde/startplasmacompositor.cmake +++ /dev/null @@ -1,202 +0,0 @@ -#!/bin/sh -# -# DEFAULT Plasma STARTUP SCRIPT ( @PROJECT_VERSION@ ) -# - -# We need to create config folder so we can write startupconfigkeys -if [ ${XDG_CONFIG_HOME} ]; then - configDir=$XDG_CONFIG_HOME; -else - configDir=${HOME}/.config; #this is the default, http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html -fi -sysConfigDirs=${XDG_CONFIG_DIRS:-/etc/xdg} - -# We need to create config folder so we can write startupconfigkeys -mkdir -p $configDir - -#This is basically setting defaults so we can use them with kstartupconfig5 -cat >$configDir/startupconfigkeys <$plasmalocalerc </dev/null 2>/dev/null; then - # Do not overwrite existing values. There is no point in setting only some - # of them as then they would not match anymore. - if [ -z "${XKB_DEFAULT_MODEL}" -a -z "${XKB_DEFAULT_LAYOUT}" -a \ - -z "${XKB_DEFAULT_VARIANT}" -a -z "${XKB_DEFAULT_OPTIONS}" ]; then - X11MODEL="$(queryLocale1 org.freedesktop.locale1.X11Model)" - X11LAYOUT="$(queryLocale1 org.freedesktop.locale1.X11Layout)" - X11VARIANT="$(queryLocale1 org.freedesktop.locale1.X11Variant)" - X11OPTIONS="$(queryLocale1 org.freedesktop.locale1.X11Options)" - [ -n "${X11MODEL}" ] && export XKB_DEFAULT_MODEL="${X11MODEL}" - [ -n "${X11LAYOUT}" ] && export XKB_DEFAULT_LAYOUT="${X11LAYOUT}" - [ -n "${X11VARIANT}" ] && export XKB_DEFAULT_VARIANT="${X11VARIANT}" - [ -n "${X11OPTIONS}" ] && export XKB_DEFAULT_OPTIONS="${X11OPTIONS}" - fi -fi - -# Source scripts found in /plasma-workspace/env/*.sh -# (where correspond to the system and user's configuration -# directories, as identified by Qt's qtpaths, e.g. $HOME/.config -# and /etc/xdg/ on Linux) -# -# This is where you can define environment variables that will be available to -# all KDE programs, so this is where you can run agents using e.g. eval `ssh-agent` -# or eval `gpg-agent --daemon`. -# Note: if you do that, you should also put "ssh-agent -k" as a shutdown script -# -# (see end of this file). -# For anything else (that doesn't set env vars, or that needs a window manager), -# better use the Autostart folder. - -scriptpath=`echo "$configDir:$sysConfigDirs" | tr ':' '\n'` - -for prefix in `echo $scriptpath`; do - for file in "$prefix"/plasma-workspace/env/*.sh; do - test -r "$file" && . "$file" || true - done -done - -echo 'startplasmacompositor: Starting up...' 1>&2 - -# Make sure that the KDE prefix is first in XDG_DATA_DIRS and that it's set at all. -# The spec allows XDG_DATA_DIRS to be not set, but X session startup scripts tend -# to set it to a list of paths *not* including the KDE prefix if it's not /usr or -# /usr/local. -if test -z "$XDG_DATA_DIRS"; then -XDG_DATA_DIRS="@KDE_INSTALL_FULL_DATADIR@:/usr/share:/usr/local/share" -fi -export XDG_DATA_DIRS - -# Make sure that D-Bus is running -if qdbus >/dev/null 2>/dev/null; then - : # ok -else - echo 'startplasmacompositor: Could not start D-Bus. Can you call qdbus?' 1>&2 - test -n "$ksplash_pid" && kill "$ksplash_pid" 2>/dev/null - exit 1 -fi - - -# Mark that full KDE session is running (e.g. Konqueror preloading works only -# with full KDE running). The KDE_FULL_SESSION property can be detected by -# any X client connected to the same X session, even if not launched -# directly from the KDE session but e.g. using "ssh -X", kdesu. $KDE_FULL_SESSION -# however guarantees that the application is launched in the same environment -# like the KDE session and that e.g. KDE utilities/libraries are available. -# KDE_FULL_SESSION property is also only available since KDE 3.5.5. -# The matching tests are: -# For $KDE_FULL_SESSION: -# if test -n "$KDE_FULL_SESSION"; then ... whatever -# For KDE_FULL_SESSION property: -# xprop -root | grep "^KDE_FULL_SESSION" >/dev/null 2>/dev/null -# if test $? -eq 0; then ... whatever -# -# Additionally there is (since KDE 3.5.7) $KDE_SESSION_UID with the uid -# of the user running the KDE session. It should be rarely needed (e.g. -# after sudo to prevent desktop-wide functionality in the new user's kded). -# -# Since KDE4 there is also KDE_SESSION_VERSION, containing the major version number. -# Note that this didn't exist in KDE3, which can be detected by its absense and -# the presence of KDE_FULL_SESSION. -# -KDE_FULL_SESSION=true -export KDE_FULL_SESSION - -KDE_SESSION_VERSION=5 -export KDE_SESSION_VERSION - -KDE_SESSION_UID=`id -ru` -export KDE_SESSION_UID - -XDG_CURRENT_DESKTOP=KDE -export XDG_CURRENT_DESKTOP - -XDG_SESSION_TYPE=wayland -export XDG_SESSION_TYPE - -# kwin_wayland can possibly also start dbus-activated services which need env variables. -# In that case, the update in startplasma might be too late. -if which dbus-update-activation-environment >/dev/null 2>/dev/null ; then - dbus-update-activation-environment --systemd --all -else - @CMAKE_INSTALL_FULL_LIBEXECDIR@/ksyncdbusenv -fi -if test $? -ne 0; then - # Startup error - echo 'startplasmacompositor: Could not sync environment to dbus.' 1>&2 - exit 1 -fi - -@KWIN_WAYLAND_BIN_PATH@ --xwayland --libinput --exit-with-session=@CMAKE_INSTALL_FULL_LIBEXECDIR@/startplasma - -echo 'startplasmacompositor: Shutting down...' 1>&2 - -unset KDE_FULL_SESSION -unset KDE_SESSION_VERSION -unset KDE_SESSION_UID - -echo 'startplasmacompositor: Done.' 1>&2