diff --git a/klipper/CMakeLists.txt b/klipper/CMakeLists.txt index ce888caee..f90e1774b 100644 --- a/klipper/CMakeLists.txt +++ b/klipper/CMakeLists.txt @@ -1,107 +1,109 @@ 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} ) diff --git a/klipper/clipcommandprocess.cpp b/klipper/clipcommandprocess.cpp index f126a6d8b..493132058 100644 --- a/klipper/clipcommandprocess.cpp +++ b/klipper/clipcommandprocess.cpp @@ -1,83 +1,83 @@ /* Copyright 2009 Esben Mose Hansen 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 "clipcommandprocess.h" #include #include "history.h" #include "historystringitem.h" #include "urlgrabber.h" ClipCommandProcess::ClipCommandProcess(const ClipAction& action, const ClipCommand& command, const QString& clip, History* history, HistoryItemConstPtr original_item) : KProcess(), m_history(history), m_historyItem(original_item), m_newhistoryItem() { QHash map; - map.insert( 's', clip ); + map.insert( QLatin1Char('s'), clip ); // support %u, %U (indicates url param(s)) and %f, %F (file param(s)) - map.insert( 'u', clip ); - map.insert( 'U', clip ); - map.insert( 'f', clip ); - map.insert( 'F', clip ); + map.insert( QLatin1Char('u'), clip ); + map.insert( QLatin1Char('U'), clip ); + map.insert( QLatin1Char('f'), clip ); + map.insert( QLatin1Char('F'), clip ); const QStringList matches = action.regExpMatches(); // support only %0 and the first 9 matches... const int numMatches = qMin(10, matches.count()); for ( int i = 0; i < numMatches; ++i ) { map.insert( QChar( '0' + i ), matches.at( i ) ); } setOutputChannelMode(OnlyStdoutChannel); setShellCommand(KMacroExpander::expandMacrosShellQuote( command.command, map ).trimmed()); connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(slotFinished(int,QProcess::ExitStatus))); if (command.output != ClipCommand::IGNORE) { connect(this, &QIODevice::readyRead, this, &ClipCommandProcess::slotStdOutputAvailable); } if (command.output != ClipCommand::REPLACE) { m_historyItem.clear(); } } void ClipCommandProcess::slotFinished(int /*exitCode*/, QProcess::ExitStatus /*newState*/) { if (m_history) { // If an history item was provided, remove it so that the new item can replace it if (m_historyItem) { m_history->remove(m_historyItem); } if (!m_newhistoryItem.isEmpty()) { m_history->insert(HistoryItemPtr(new HistoryStringItem(m_newhistoryItem))); } } deleteLater(); } void ClipCommandProcess::slotStdOutputAvailable() { m_newhistoryItem.append(QString::fromLocal8Bit(this->readAllStandardOutput())); } diff --git a/klipper/klipper.cpp b/klipper/klipper.cpp index 89740af5f..97de688e3 100644 --- a/klipper/klipper.cpp +++ b/klipper/klipper.cpp @@ -1,1046 +1,1046 @@ /* This file is part of the KDE project Copyright (C) by Andrew Stanley-Jones Copyright (C) 2000 by Carsten Pfeiffer Copyright (C) 2004 Esben Mose Hansen Copyright (C) 2008 by Dmitry Suzdalev 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "klipper.h" #include #include "klipper_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "configdialog.h" #include "klippersettings.h" #include "urlgrabber.h" #include "history.h" #include "historyitem.h" #include "historymodel.h" #include "historystringitem.h" #include "klipperpopup.h" #ifdef HAVE_PRISON #include #endif #include #if HAVE_X11 #include #include #endif namespace { /** * Use this when manipulating the clipboard * from within clipboard-related signals. * * This avoids issues such as mouse-selections that immediately * disappear. * pattern: Resource Acqusition is Initialisation (RAII) * * (This is not threadsafe, so don't try to use such in threaded * applications). */ struct Ignore { Ignore(int& locklevel) : locklevelref(locklevel) { locklevelref++; } ~Ignore() { locklevelref--; } private: int& locklevelref; }; } // config == KGlobal::config for process, otherwise applet Klipper::Klipper(QObject* parent, const KSharedConfigPtr& config, KlipperMode mode) : QObject( parent ) , m_overflowCounter( 0 ) , m_locklevel( 0 ) , m_config( config ) , m_pendingContentsCheck( false ) , m_mode(mode) { if (m_mode == KlipperMode::Standalone) { setenv("KSNI_NO_DBUSMENU", "1", 1); } QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.klipper")); QDBusConnection::sessionBus().registerObject(QStringLiteral("/klipper"), this, QDBusConnection::ExportScriptableSlots); updateTimestamp(); // read initial X user time m_clip = qApp->clipboard(); connect( m_clip, &QClipboard::changed, this, &Klipper::newClipData ); connect( &m_overflowClearTimer, &QTimer::timeout, this, &Klipper::slotClearOverflow); m_pendingCheckTimer.setSingleShot( true ); connect( &m_pendingCheckTimer, &QTimer::timeout, this, &Klipper::slotCheckPending); m_history = new History( this ); m_popup = new KlipperPopup(m_history); m_popup->setShowHelp(m_mode == KlipperMode::Standalone); connect(m_history, &History::changed, m_popup, &KlipperPopup::slotHistoryChanged); connect(m_history, &History::topIsUserSelectedSet, m_popup, &KlipperPopup::slotTopIsUserSelectedSet); // we need that collection, otherwise KToggleAction is not happy :} m_collection = new KActionCollection( this ); m_toggleURLGrabAction = new KToggleAction( this ); m_collection->addAction( QStringLiteral("clipboard_action"), m_toggleURLGrabAction ); m_toggleURLGrabAction->setText(i18n("Enable Clipboard Actions")); KGlobalAccel::setGlobalShortcut(m_toggleURLGrabAction, QKeySequence(Qt::ALT+Qt::CTRL+Qt::Key_X)); connect( m_toggleURLGrabAction, &QAction::toggled, this, &Klipper::setURLGrabberEnabled); /* * Create URL grabber */ m_myURLGrabber = new URLGrabber(m_history); connect( m_myURLGrabber, &URLGrabber::sigPopup, this, &Klipper::showPopupMenu ); connect( m_myURLGrabber, &URLGrabber::sigDisablePopup, this, &Klipper::disableURLGrabber ); /* * Load configuration settings */ loadSettings(); // load previous history if configured if (m_bKeepContents) { loadHistory(); } m_clearHistoryAction = m_collection->addAction( QStringLiteral("clear-history") ); m_clearHistoryAction->setIcon( QIcon::fromTheme(QStringLiteral("edit-clear-history")) ); m_clearHistoryAction->setText( i18n("C&lear Clipboard History") ); KGlobalAccel::setGlobalShortcut(m_clearHistoryAction, QKeySequence()); connect(m_clearHistoryAction, &QAction::triggered, this, &Klipper::slotAskClearHistory); QString CONFIGURE=QStringLiteral("configure"); m_configureAction = m_collection->addAction( CONFIGURE ); m_configureAction->setIcon( QIcon::fromTheme(CONFIGURE) ); m_configureAction->setText( i18n("&Configure Klipper...") ); connect(m_configureAction, &QAction::triggered, this, &Klipper::slotConfigure); m_quitAction = m_collection->addAction( QStringLiteral("quit") ); m_quitAction->setIcon( QIcon::fromTheme(QStringLiteral("application-exit")) ); m_quitAction->setText( i18nc("@item:inmenu Quit Klipper", "&Quit") ); connect(m_quitAction, &QAction::triggered, this, &Klipper::slotQuit); m_repeatAction = m_collection->addAction(QStringLiteral("repeat_action")); m_repeatAction->setText(i18n("Manually Invoke Action on Current Clipboard")); KGlobalAccel::setGlobalShortcut(m_repeatAction, QKeySequence(Qt::ALT+Qt::CTRL+Qt::Key_R)); connect(m_repeatAction, &QAction::triggered, this, &Klipper::slotRepeatAction); // add an edit-possibility m_editAction = m_collection->addAction(QStringLiteral("edit_clipboard")); m_editAction->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); m_editAction->setText(i18n("&Edit Contents...")); KGlobalAccel::setGlobalShortcut(m_editAction, QKeySequence()); connect(m_editAction, &QAction::triggered, this, [this]() { editData(m_history->first()); } ); #ifdef HAVE_PRISON // add barcode for mobile phones m_showBarcodeAction = m_collection->addAction(QStringLiteral("show-barcode")); m_showBarcodeAction->setText(i18n("&Show Barcode...")); KGlobalAccel::setGlobalShortcut(m_showBarcodeAction, QKeySequence()); connect(m_showBarcodeAction, &QAction::triggered, this, [this]() { showBarcode(m_history->first()); } ); #endif // Cycle through history m_cycleNextAction = m_collection->addAction(QStringLiteral("cycleNextAction")); m_cycleNextAction->setText(i18n("Next History Item")); KGlobalAccel::setGlobalShortcut(m_cycleNextAction, QKeySequence()); connect(m_cycleNextAction, &QAction::triggered, this, &Klipper::slotCycleNext); m_cyclePrevAction = m_collection->addAction(QStringLiteral("cyclePrevAction")); m_cyclePrevAction->setText(i18n("Previous History Item")); KGlobalAccel::setGlobalShortcut(m_cyclePrevAction, QKeySequence()); connect(m_cyclePrevAction, &QAction::triggered, this, &Klipper::slotCyclePrev); // Action to show Klipper popup on mouse position m_showOnMousePos = m_collection->addAction(QStringLiteral("show-on-mouse-pos")); m_showOnMousePos->setText(i18n("Open Klipper at Mouse Position")); KGlobalAccel::setGlobalShortcut(m_showOnMousePos, QKeySequence()); connect(m_showOnMousePos, &QAction::triggered, this, &Klipper::slotPopupMenu); connect ( history(), &History::topChanged, this, &Klipper::slotHistoryTopChanged ); connect( m_popup, &QMenu::aboutToShow, this, &Klipper::slotStartShowTimer ); if (m_mode == KlipperMode::Standalone) { m_popup->plugAction( m_toggleURLGrabAction ); m_popup->plugAction( m_clearHistoryAction ); m_popup->plugAction( m_configureAction ); m_popup->plugAction( m_repeatAction ); m_popup->plugAction( m_editAction ); #ifdef HAVE_PRISON m_popup->plugAction( m_showBarcodeAction ); #endif m_popup->plugAction( m_quitAction ); } // session manager interaction if (m_mode == KlipperMode::Standalone) { connect(qApp, &QGuiApplication::commitDataRequest, this, &Klipper::saveSession); } connect(this, &Klipper::passivePopup, this, [this] (const QString &caption, const QString &text) { if (m_notification) { m_notification->setTitle(caption); m_notification->setText(text); } else { m_notification = KNotification::event(KNotification::Notification, caption, text, QStringLiteral("klipper")); } } ); } Klipper::~Klipper() { delete m_myURLGrabber; } // DBUS QString Klipper::getClipboardContents() { return getClipboardHistoryItem(0); } void Klipper::showKlipperPopupMenu() { slotPopupMenu(); } void Klipper::showKlipperManuallyInvokeActionMenu() { slotRepeatAction(); } // DBUS - don't call from Klipper itself void Klipper::setClipboardContents(const QString &s) { if (s.isEmpty()) return; Ignore lock( m_locklevel ); updateTimestamp(); HistoryItemPtr item(HistoryItemPtr(new HistoryStringItem(s))); setClipboard( *item, Clipboard | Selection); history()->insert( item ); } // DBUS - don't call from Klipper itself void Klipper::clearClipboardContents() { updateTimestamp(); slotClearClipboard(); } // DBUS - don't call from Klipper itself void Klipper::clearClipboardHistory() { updateTimestamp(); slotClearClipboard(); history()->slotClear(); saveSession(); } // DBUS - don't call from Klipper itself void Klipper::saveClipboardHistory() { if ( m_bKeepContents ) { // save the clipboard eventually saveHistory(); } } void Klipper::slotStartShowTimer() { m_showTimer.start(); } void Klipper::loadSettings() { // Security bug 142882: If user has save clipboard turned off, old data should be deleted from disk static bool firstrun = true; if (!firstrun && m_bKeepContents && !KlipperSettings::keepClipboardContents()) { saveHistory(true); } firstrun=false; m_bKeepContents = KlipperSettings::keepClipboardContents(); m_bReplayActionInHistory = KlipperSettings::replayActionInHistory(); m_bNoNullClipboard = KlipperSettings::preventEmptyClipboard(); // 0 is the id of "Ignore selection" radiobutton m_bIgnoreSelection = KlipperSettings::ignoreSelection(); m_bIgnoreImages = KlipperSettings::ignoreImages(); m_bSynchronize = KlipperSettings::syncClipboards(); // NOTE: not used atm - kregexpeditor is not ported to kde4 m_bUseGUIRegExpEditor = KlipperSettings::useGUIRegExpEditor(); m_bSelectionTextOnly = KlipperSettings::selectionTextOnly(); m_bURLGrabber = KlipperSettings::uRLGrabberEnabled(); // this will cause it to loadSettings too setURLGrabberEnabled(m_bURLGrabber); history()->setMaxSize( KlipperSettings::maxClipItems() ); // Convert 4.3 settings if (KlipperSettings::synchronize() != 3) { // 2 was the id of "Ignore selection" radiobutton m_bIgnoreSelection = KlipperSettings::synchronize() == 2; // 0 was the id of "Synchronize contents" radiobutton m_bSynchronize = KlipperSettings::synchronize() == 0; KConfigSkeletonItem* item = KlipperSettings::self()->findItem(QStringLiteral("SyncClipboards")); item->setProperty(m_bSynchronize); item = KlipperSettings::self()->findItem(QStringLiteral("IgnoreSelection")); item->setProperty(m_bIgnoreSelection); item = KlipperSettings::self()->findItem(QStringLiteral("Synchronize")); // Mark property as converted. item->setProperty(3); KlipperSettings::self()->save(); KlipperSettings::self()->load(); } if (m_bKeepContents && !m_saveFileTimer) { m_saveFileTimer = new QTimer(this); m_saveFileTimer->setSingleShot(true); m_saveFileTimer->setInterval(5000); connect(m_saveFileTimer, &QTimer::timeout, this, [this] { QtConcurrent::run(this, &Klipper::saveHistory, false); } ); connect(m_history, &History::changed, m_saveFileTimer, static_cast(&QTimer::start)); } else { delete m_saveFileTimer; m_saveFileTimer = nullptr; } } void Klipper::saveSettings() const { m_myURLGrabber->saveSettings(); KlipperSettings::self()->setVersion(QStringLiteral(KLIPPER_VERSION_STRING)); KlipperSettings::self()->save(); // other settings should be saved automatically by KConfigDialog } void Klipper::showPopupMenu( QMenu* menu ) { Q_ASSERT( menu != nullptr ); QSize size = menu->sizeHint(); // geometry is not valid until it's shown QPoint pos = QCursor::pos(); // ### We can't know where the systray icon is (since it can be hidden or shown // in several places), so the cursor position is the only option. if ( size.height() < pos.y() ) pos.ry() -= size.height(); menu->popup(pos); } bool Klipper::loadHistory() { static const char failed_load_warning[] = "Failed to load history resource. Clipboard history cannot be read."; // don't use "appdata", klipper is also a kicker applet QFile history_file(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("klipper/history2.lst"))); if ( !history_file.exists() ) { qCWarning(KLIPPER_LOG) << failed_load_warning << ": " << "History file does not exist" ; return false; } if ( !history_file.open( QIODevice::ReadOnly ) ) { qCWarning(KLIPPER_LOG) << failed_load_warning << ": " << history_file.errorString() ; return false; } QDataStream file_stream( &history_file ); if( file_stream.atEnd()) { qCWarning(KLIPPER_LOG) << failed_load_warning << ": " << "Error in reading data" ; return false; } QByteArray data; quint32 crc; file_stream >> crc >> data; if( crc32( 0, reinterpret_cast( data.data() ), data.size() ) != crc ) { qCWarning(KLIPPER_LOG) << failed_load_warning << ": " << "CRC checksum does not match" ; return false; } QDataStream history_stream( &data, QIODevice::ReadOnly ); char* version; history_stream >> version; delete[] version; // The list needs to be reversed, as it is saved // youngest-first to keep the most important clipboard // items at the top, but the history is created oldest // first. QVector reverseList; for ( HistoryItemPtr item = HistoryItem::create( history_stream ); !item.isNull(); item = HistoryItem::create( history_stream ) ) { reverseList.prepend( item ); } history()->slotClear(); for ( auto it = reverseList.constBegin(); it != reverseList.constEnd(); ++it ) { history()->forceInsert(*it); } if ( !history()->empty() ) { setClipboard( *history()->first(), Clipboard | Selection ); } return true; } void Klipper::saveHistory(bool empty) { QMutexLocker lock(m_history->model()->mutex()); static const char failed_save_warning[] = "Failed to save history. Clipboard history cannot be saved."; // don't use "appdata", klipper is also a kicker applet QString history_file_name(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("klipper/history2.lst"))); if ( history_file_name.isNull() || history_file_name.isEmpty() ) { // try creating the file QDir dir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)); if (!dir.mkpath(QStringLiteral("klipper"))) { qCWarning(KLIPPER_LOG) << failed_save_warning ; return; } history_file_name = dir.absoluteFilePath(QStringLiteral("klipper/history2.lst")); } if ( history_file_name.isNull() || history_file_name.isEmpty() ) { qCWarning(KLIPPER_LOG) << failed_save_warning ; return; } QSaveFile history_file( history_file_name ); if (!history_file.open(QIODevice::WriteOnly)) { qCWarning(KLIPPER_LOG) << failed_save_warning ; return; } QByteArray data; QDataStream history_stream( &data, QIODevice::WriteOnly ); history_stream << KLIPPER_VERSION_STRING; // const char* if (!empty) { HistoryItemConstPtr item = history()->first(); if (item) { do { history_stream << item.data(); item = HistoryItemConstPtr(history()->find(item->next_uuid())); } while (item != history()->first()); } } quint32 crc = crc32( 0, reinterpret_cast( data.data() ), data.size() ); QDataStream ds ( &history_file ); ds << crc << data; if (!history_file.commit()) { qCWarning(KLIPPER_LOG) << failed_save_warning ; } } // save session on shutdown. Don't simply use the c'tor, as that may not be called. void Klipper::saveSession() { if ( m_bKeepContents ) { // save the clipboard eventually saveHistory(); } saveSettings(); } void Klipper::disableURLGrabber() { KMessageBox::information( nullptr, i18n( "You can enable URL actions later by left-clicking on the " "Klipper icon and selecting 'Enable Clipboard Actions'" ) ); setURLGrabberEnabled( false ); } void Klipper::slotConfigure() { if (KConfigDialog::showDialog(QStringLiteral("preferences"))) { return; } ConfigDialog *dlg = new ConfigDialog( nullptr, KlipperSettings::self(), this, m_collection ); connect(dlg, &KConfigDialog::settingsChanged, this, &Klipper::loadSettings); dlg->show(); } void Klipper::slotQuit() { // If the menu was just opened, likely the user // selected quit by accident while attempting to // click the Klipper icon. if ( m_showTimer.elapsed() < 300 ) { return; } saveSession(); int autoStart = KMessageBox::questionYesNoCancel(nullptr, i18n("Should Klipper start automatically when you login?"), i18n("Automatically Start Klipper?"), KGuiItem(i18n("Start")), KGuiItem(i18n("Do Not Start")), KStandardGuiItem::cancel(), QStringLiteral("StartAutomatically")); KConfigGroup config( KSharedConfig::openConfig(), "General"); if ( autoStart == KMessageBox::Yes ) { config.writeEntry("AutoStart", true); } else if ( autoStart == KMessageBox::No) { config.writeEntry("AutoStart", false); } else // cancel chosen don't quit return; config.sync(); qApp->quit(); } void Klipper::slotPopupMenu() { m_popup->ensureClean(); m_popup->slotSetTopActive(); showPopupMenu( m_popup ); } void Klipper::slotRepeatAction() { auto top = qSharedPointerCast( history()->first() ); if ( top ) { m_myURLGrabber->invokeAction( top ); } } void Klipper::setURLGrabberEnabled( bool enable ) { if (enable != m_bURLGrabber) { m_bURLGrabber = enable; m_lastURLGrabberTextSelection.clear(); m_lastURLGrabberTextClipboard.clear(); KlipperSettings::setURLGrabberEnabled(enable); } m_toggleURLGrabAction->setChecked( enable ); // make it update its settings m_myURLGrabber->loadSettings(); } void Klipper::slotHistoryTopChanged() { if ( m_locklevel ) { return; } auto topitem = history()->first(); if ( topitem ) { setClipboard( *topitem, Clipboard | Selection ); } if ( m_bReplayActionInHistory && m_bURLGrabber ) { slotRepeatAction(); } } void Klipper::slotClearClipboard() { Ignore lock( m_locklevel ); m_clip->clear(QClipboard::Selection); m_clip->clear(QClipboard::Clipboard); } HistoryItemPtr Klipper::applyClipChanges( const QMimeData* clipData ) { if ( m_locklevel ) { return HistoryItemPtr(); } Ignore lock( m_locklevel ); HistoryItemPtr item = HistoryItem::create( clipData ); - if (clipData->data("x-kde-passwordManagerHint") != QByteArrayLiteral("secret")) { + if (clipData->data(QStringLiteral("x-kde-passwordManagerHint")) != QByteArrayLiteral("secret")) { history()->insert( item ); } return item; } void Klipper::newClipData( QClipboard::Mode mode ) { if ( m_locklevel ) { return; } if( mode == QClipboard::Selection && blockFetchingNewData()) return; checkClipData( mode == QClipboard::Selection ? true : false ); } // Protection against too many clipboard data changes. Lyx responds to clipboard data // requests with setting new clipboard data, so if Lyx takes over clipboard, // Klipper notices, requests this data, this triggers "new" clipboard contents // from Lyx, so Klipper notices again, requests this data, ... you get the idea. const int MAX_CLIPBOARD_CHANGES = 10; // max changes per second bool Klipper::blockFetchingNewData() { #if HAVE_X11 // Hacks for #85198 and #80302. // #85198 - block fetching new clipboard contents if Shift is pressed and mouse is not, // this may mean the user is doing selection using the keyboard, in which case // it's possible the app sets new clipboard contents after every change - Klipper's // history would list them all. // #80302 - OOo (v1.1.3 at least) has a bug that if Klipper requests its clipboard contents // while the user is doing a selection using the mouse, OOo stops updating the clipboard // contents, so in practice it's like the user has selected only the part which was // selected when Klipper asked first. // Use XQueryPointer rather than QApplication::mouseButtons()/keyboardModifiers(), because // Klipper needs the very current state. if (!KWindowSystem::isPlatformX11()) { return false; } xcb_connection_t *c = QX11Info::connection(); const xcb_query_pointer_cookie_t cookie = xcb_query_pointer_unchecked(c, QX11Info::appRootWindow()); QScopedPointer queryPointer(xcb_query_pointer_reply(c, cookie, nullptr)); if (queryPointer.isNull()) { return false; } if (((queryPointer->mask & (XCB_KEY_BUT_MASK_SHIFT | XCB_KEY_BUT_MASK_BUTTON_1)) == XCB_KEY_BUT_MASK_SHIFT) // BUG: 85198 || ((queryPointer->mask & XCB_KEY_BUT_MASK_BUTTON_1) == XCB_KEY_BUT_MASK_BUTTON_1)) { // BUG: 80302 m_pendingContentsCheck = true; m_pendingCheckTimer.start( 100 ); return true; } m_pendingContentsCheck = false; if ( m_overflowCounter == 0 ) m_overflowClearTimer.start( 1000 ); if( ++m_overflowCounter > MAX_CLIPBOARD_CHANGES ) return true; #endif return false; } void Klipper::slotCheckPending() { if( !m_pendingContentsCheck ) return; m_pendingContentsCheck = false; // blockFetchingNewData() will be called again updateTimestamp(); newClipData( QClipboard::Selection ); // always selection } void Klipper::checkClipData( bool selectionMode ) { if ( ignoreClipboardChanges() ) // internal to klipper, ignoring QSpinBox selections { // keep our old clipboard, thanks // This won't quite work, but it's close enough for now. // The trouble is that the top selection =! top clipboard // but we don't track that yet. We will.... auto top = history()->first(); if ( top ) { setClipboard( *top, selectionMode ? Selection : Clipboard); } return; } qCDebug(KLIPPER_LOG) << "Checking clip data"; const QMimeData* data = m_clip->mimeData( selectionMode ? QClipboard::Selection : QClipboard::Clipboard ); if ( !data ) { qCWarning(KLIPPER_LOG) << "No data in clipboard. This not not supposed to happen."; return; } bool changed = true; // ### FIXME (only relevant under polling, might be better to simply remove polling and rely on XFixes) bool clipEmpty = data->formats().isEmpty(); if (clipEmpty) { // Might be a timeout. Try again clipEmpty = data->formats().isEmpty(); qCDebug(KLIPPER_LOG) << "was empty. Retried, now " << (clipEmpty?" still empty":" no longer empty"); } if ( changed && clipEmpty && m_bNoNullClipboard ) { auto top = history()->first(); if ( top ) { // keep old clipboard after someone set it to null qCDebug(KLIPPER_LOG) << "Resetting clipboard (Prevent empty clipboard)"; setClipboard( *top, selectionMode ? Selection : Clipboard ); } return; } // this must be below the "bNoNullClipboard" handling code! // XXX: I want a better handling of selection/clipboard in general. // XXX: Order sensitive code. Must die. if ( selectionMode && m_bIgnoreSelection ) return; if( selectionMode && m_bSelectionTextOnly && !data->hasText()) return; if( data->hasUrls() ) ; // ok else if( data->hasText() ) ; // ok else if( data->hasImage() ) { if( m_bIgnoreImages ) return; } else // unknown, ignore return; HistoryItemPtr item = applyClipChanges( data ); if (changed) { qCDebug(KLIPPER_LOG) << "Synchronize?" << m_bSynchronize; if ( m_bSynchronize && item ) { setClipboard( *item, selectionMode ? Clipboard : Selection ); } } QString& lastURLGrabberText = selectionMode ? m_lastURLGrabberTextSelection : m_lastURLGrabberTextClipboard; if( m_bURLGrabber && item && data->hasText()) { m_myURLGrabber->checkNewData( qSharedPointerConstCast(item) ); // Make sure URLGrabber doesn't repeat all the time if klipper reads the same // text all the time (e.g. because XFixes is not available and the application // has broken TIMESTAMP target). Using most recent history item may not always // work. if ( item->text() != lastURLGrabberText ) { lastURLGrabberText = item->text(); } } else { lastURLGrabberText.clear(); } } void Klipper::setClipboard( const HistoryItem& item, int mode ) { Ignore lock( m_locklevel ); Q_ASSERT( ( mode & 1 ) == 0 ); // Warn if trying to pass a boolean as a mode. if ( mode & Selection ) { qCDebug(KLIPPER_LOG) << "Setting selection to <" << item.text() << ">"; m_clip->setMimeData( item.mimeData(), QClipboard::Selection ); } if ( mode & Clipboard ) { qCDebug(KLIPPER_LOG) << "Setting clipboard to <" << item.text() << ">"; m_clip->setMimeData( item.mimeData(), QClipboard::Clipboard ); } } void Klipper::slotClearOverflow() { m_overflowClearTimer.stop(); if( m_overflowCounter > MAX_CLIPBOARD_CHANGES ) { qCDebug(KLIPPER_LOG) << "App owning the clipboard/selection is lame"; // update to the latest data - this unfortunately may trigger the problem again newClipData( QClipboard::Selection ); // Always the selection. } m_overflowCounter = 0; } QStringList Klipper::getClipboardHistoryMenu() { QStringList menu; auto item = history()->first(); if (item) { do { menu << item->text(); item = history()->find(item->next_uuid()); } while (item != history()->first()); } return menu; } QString Klipper::getClipboardHistoryItem(int i) { auto item = history()->first(); if (item) { do { if (i-- == 0) { return item->text(); } item = history()->find(item->next_uuid()); } while (item != history()->first()); } return QString(); } // // changing a spinbox in klipper's config-dialog causes the lineedit-contents // of the spinbox to be selected and hence the clipboard changes. But we don't // want all those items in klipper's history. See #41917 // bool Klipper::ignoreClipboardChanges() const { QWidget *focusWidget = qApp->focusWidget(); if ( focusWidget ) { if ( focusWidget->inherits( "QSpinBox" ) || (focusWidget->parentWidget() && focusWidget->inherits("QLineEdit") && focusWidget->parentWidget()->inherits("QSpinWidget")) ) { return true; } } return false; } void Klipper::updateTimestamp() { #if HAVE_X11 if (KWindowSystem::isPlatformX11()) { QX11Info::setAppTime(QX11Info::getTimestamp()); } #endif } void Klipper::editData(const QSharedPointer< const HistoryItem > &item) { QPointer dlg(new QDialog()); dlg->setWindowTitle( i18n("Edit Contents") ); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, dlg); buttons->button(QDialogButtonBox::Ok)->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttons, &QDialogButtonBox::accepted, dlg.data(), &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, dlg.data(), &QDialog::reject); connect(dlg.data(), &QDialog::finished, dlg.data(), [this, dlg, item](int result) { emit editFinished(item, result); dlg->deleteLater(); } ); KTextEdit *edit = new KTextEdit( dlg ); edit->setAcceptRichText(false); if (item) { edit->setPlainText( item->text() ); } edit->setFocus(); edit->setMinimumSize( 300, 40 ); QVBoxLayout *layout = new QVBoxLayout(dlg); layout->addWidget(edit); layout->addWidget(buttons); dlg->adjustSize(); connect(dlg.data(), &QDialog::accepted, this, [this, edit, item]() { QString text = edit->toPlainText(); if (item) { m_history->remove( item ); } m_history->insert(HistoryItemPtr(new HistoryStringItem(text))); if (m_myURLGrabber) { m_myURLGrabber->checkNewData(HistoryItemConstPtr(m_history->first())); } }); if (m_mode == KlipperMode::Standalone) { dlg->setModal(true); dlg->exec(); } else if (m_mode == KlipperMode::DataEngine) { dlg->open(); } } #ifdef HAVE_PRISON class BarcodeLabel : public QLabel { public: BarcodeLabel(Prison::AbstractBarcode *barcode, QWidget *parent = nullptr) : QLabel(parent) , m_barcode(barcode) { setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); setPixmap(QPixmap::fromImage(m_barcode->toImage(size()))); } protected: void resizeEvent(QResizeEvent *event) override { QLabel::resizeEvent(event); setPixmap(QPixmap::fromImage(m_barcode->toImage(event->size()))); } private: QScopedPointer m_barcode; }; void Klipper::showBarcode(const QSharedPointer< const HistoryItem > &item) { using namespace Prison; QPointer dlg(new QDialog()); dlg->setWindowTitle( i18n("Mobile Barcode") ); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok, dlg); buttons->button(QDialogButtonBox::Ok)->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttons, &QDialogButtonBox::accepted, dlg.data(), &QDialog::accept); connect(dlg.data(), &QDialog::finished, dlg.data(), &QDialog::deleteLater); QWidget* mw = new QWidget(dlg); QHBoxLayout* layout = new QHBoxLayout(mw); { AbstractBarcode *qrCode = createBarcode(QRCode); if (qrCode) { if(item) { qrCode->setData(item->text()); } BarcodeLabel *qrCodeLabel = new BarcodeLabel(qrCode, mw); layout->addWidget(qrCodeLabel); } } { AbstractBarcode *dataMatrix = createBarcode(DataMatrix); if (dataMatrix) { if (item) { dataMatrix->setData(item->text()); } BarcodeLabel *dataMatrixLabel = new BarcodeLabel(dataMatrix, mw); layout->addWidget(dataMatrixLabel); } } mw->setFocus(); QVBoxLayout *vBox = new QVBoxLayout(dlg); vBox->addWidget(mw); vBox->addWidget(buttons); dlg->adjustSize(); if (m_mode == KlipperMode::Standalone) { dlg->setModal(true); dlg->exec(); } else if (m_mode == KlipperMode::DataEngine) { dlg->open(); } } #endif //HAVE_PRISON void Klipper::slotAskClearHistory() { int clearHist = KMessageBox::questionYesNo(nullptr, i18n("Really delete entire clipboard history?"), i18n("Delete clipboard history?"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("really_clear_history"), KMessageBox::Dangerous); if (clearHist == KMessageBox::Yes) { history()->slotClear(); slotClearClipboard(); saveHistory(); } } void Klipper::slotCycleNext() { //do cycle and show popup only if we have something in clipboard if (m_history->first()) { m_history->cycleNext(); emit passivePopup(i18n("Clipboard history"), cycleText()); } } void Klipper::slotCyclePrev() { //do cycle and show popup only if we have something in clipboard if (m_history->first()) { m_history->cyclePrev(); emit passivePopup(i18n("Clipboard history"), cycleText()); } } QString Klipper::cycleText() const { const int WIDTH_IN_PIXEL = 400; auto itemprev = m_history->prevInCycle(); auto item = m_history->first(); auto itemnext = m_history->nextInCycle(); QFontMetrics font_metrics(m_popup->fontMetrics()); QString result(QStringLiteral("")); if (itemprev) { result += QLatin1String(""); } result += QLatin1String(""); if (itemnext) { result += QLatin1String(""); } result += QLatin1String("
"); result += i18n("up"); result += QLatin1String(""); result += font_metrics.elidedText(itemprev->text().simplified().toHtmlEscaped(), Qt::ElideMiddle, WIDTH_IN_PIXEL); result += QLatin1String("
"); result += i18n("current"); result += QLatin1String(""); result += font_metrics.elidedText(item->text().simplified().toHtmlEscaped(), Qt::ElideMiddle, WIDTH_IN_PIXEL); result += QLatin1String("
"); result += i18n("down"); result += QLatin1String(""); result += font_metrics.elidedText(itemnext->text().simplified().toHtmlEscaped(), Qt::ElideMiddle, WIDTH_IN_PIXEL); result += QLatin1String("
"); return result; } diff --git a/klipper/popupproxy.cpp b/klipper/popupproxy.cpp index 316d50c74..4e94781c0 100644 --- a/klipper/popupproxy.cpp +++ b/klipper/popupproxy.cpp @@ -1,179 +1,179 @@ /* This file is part of the KDE project Copyright (C) 2004 Esben Mose Hansen 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "popupproxy.h" #include #include #include #include #include #include "historyitem.h" #include "history.h" #include "klipperpopup.h" PopupProxy::PopupProxy( KlipperPopup* parent, int menu_height, int menu_width ) : QObject( parent ), m_proxy_for_menu( parent ), m_spill_uuid(), m_menu_height( menu_height ), m_menu_width( menu_width ) { if (!parent->history()->empty()) { m_spill_uuid = parent->history()->first()->uuid(); } connect( parent->history(), &History::changed, this, &PopupProxy::slotHistoryChanged ); connect(m_proxy_for_menu, SIGNAL(triggered(QAction*)), parent->history(), SLOT(slotMoveToTop(QAction*))); } void PopupProxy::slotHistoryChanged() { deleteMoreMenus(); } void PopupProxy::deleteMoreMenus() { const QMenu* myParent = parent(); if ( myParent != m_proxy_for_menu ) { QMenu* delme = m_proxy_for_menu; m_proxy_for_menu = static_cast( m_proxy_for_menu->parent() ); while ( m_proxy_for_menu != myParent ) { delme = m_proxy_for_menu; m_proxy_for_menu = static_cast( m_proxy_for_menu->parent() ); } // We are called probably from within the menus event-handler (triggered=>slotMoveToTop=>changed=>slotHistoryChanged=>deleteMoreMenus) // what can result in a crash if we just delete the menu here (#155196 and #165154) So, delay the delete. delme->deleteLater(); } } int PopupProxy::buildParent( int index, const QRegExp& filter ) { deleteMoreMenus(); // Start from top of history (again) m_spill_uuid = parent()->history()->empty() ? QByteArray() : parent()->history()->first()->uuid(); if ( filter.isValid() ) { m_filter = filter; } return insertFromSpill( index ); } KlipperPopup* PopupProxy::parent() { return static_cast( QObject::parent() ); } void PopupProxy::slotAboutToShow() { insertFromSpill(); } void PopupProxy::tryInsertItem( HistoryItem const * const item, int& remainingHeight, const int index ) { QAction *action = new QAction(m_proxy_for_menu); QPixmap image( item->image() ); if ( image.isNull() ) { // Squeeze text strings so that do not take up the entire screen (or more) QString text = m_proxy_for_menu->fontMetrics().elidedText( item->text().simplified(), Qt::ElideMiddle, m_menu_width ); - text.replace( '&', QLatin1String("&&") ); + text.replace( QLatin1Char('&'), QLatin1String("&&") ); action->setText(text); } else { #if 0 // not used because QAction#setIcon does not respect this size; it does scale anyway. TODO: find a way to set a bigger image const QSize max_size( m_menu_width,m_menu_height/4 ); if ( image.height() > max_size.height() || image.width() > max_size.width() ) { image = image.scaled( max_size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); } #endif action->setIcon(QIcon(image)); } action->setData(item->uuid()); // if the m_proxy_for_menu is a submenu (aka a "More" menu) then it may the case, that there is no other action in that menu yet. - QAction *before = index < m_proxy_for_menu->actions().count() ? m_proxy_for_menu->actions().at(index) : 0; + QAction *before = index < m_proxy_for_menu->actions().count() ? m_proxy_for_menu->actions().at(index) : nullptr; // insert the new action to the m_proxy_for_menu m_proxy_for_menu->insertAction(before, action); // Determine height of a menu item. QStyleOptionMenuItem style_options; // It would be much easier to use QMenu::initStyleOptions. But that is protected, so until we have a better // excuse to subclass that, I'd rather implement this manually. // Note 2 properties, tabwidth and maxIconWidth, are not available from the public interface, so those are left out (probably not // important for height. Also, Exlsive checkType is disregarded as I don't think we will ever use it) style_options.initFrom(m_proxy_for_menu); style_options.checkType = action->isCheckable() ? QStyleOptionMenuItem::NonExclusive : QStyleOptionMenuItem::NotCheckable; style_options.checked = action->isChecked(); style_options.font = action->font(); style_options.icon = action->icon(); style_options.menuHasCheckableItems = true; style_options.menuRect = m_proxy_for_menu->rect(); style_options.text = action->text(); int font_height = QFontMetrics(m_proxy_for_menu->fontMetrics()).height(); int itemheight = m_proxy_for_menu->style()->sizeFromContents(QStyle::CT_MenuItem, &style_options, QSize( 0, font_height ), m_proxy_for_menu).height(); // Subtract the used height remainingHeight -= itemheight; } int PopupProxy::insertFromSpill( int index ) { const History* history = parent()->history(); // This menu is going to be filled, so we don't need the aboutToShow() // signal anymore disconnect( m_proxy_for_menu, nullptr, this, nullptr ); // Insert history items into the current m_proxy_for_menu, // discarding any that doesn't match the current filter. // stop when the total number of items equal m_itemsPerMenu; int count = 0; int remainingHeight = m_menu_height - m_proxy_for_menu->sizeHint().height(); auto item = history->find(m_spill_uuid); if (!item) { return count; } do { if ( m_filter.indexIn( item->text() ) != -1) { tryInsertItem( item.data(), remainingHeight, index++ ); count++; } item = history->find(item->next_uuid()); } while ( item && history->first() != item && remainingHeight >= 0); m_spill_uuid = item->uuid(); // If there is more items in the history, insert a new "More..." menu and // make *this a proxy for that menu ('s content). if (history->first() && m_spill_uuid != history->first()->uuid()) { QMenu* moreMenu = new QMenu(i18n("&More"), m_proxy_for_menu); connect(moreMenu, &QMenu::aboutToShow, this, &PopupProxy::slotAboutToShow); - QAction *before = index < m_proxy_for_menu->actions().count() ? m_proxy_for_menu->actions().at(index) : 0; + QAction *before = index < m_proxy_for_menu->actions().count() ? m_proxy_for_menu->actions().at(index) : nullptr; m_proxy_for_menu->insertMenu(before, moreMenu); m_proxy_for_menu = moreMenu; } // Return the number of items inserted. return count; } diff --git a/klipper/tray.cpp b/klipper/tray.cpp index 80f93aced..071404a1c 100644 --- a/klipper/tray.cpp +++ b/klipper/tray.cpp @@ -1,65 +1,65 @@ /* This file is part of the KDE project Copyright (C) by Andrew Stanley-Jones Copyright (C) 2000 by Carsten Pfeiffer Copyright (C) 2004 Esben Mose Hansen 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "tray.h" #include #include "klipper.h" #include "history.h" #include "historyitem.h" #include "klipperpopup.h" KlipperTray::KlipperTray() : KStatusNotifierItem() { setTitle( i18n( "Klipper" ) ); const QString klipperIconName = QStringLiteral("klipper"); setIconByName( klipperIconName ); setToolTip( klipperIconName, i18n( "Clipboard Contents" ), i18n( "Clipboard is empty" ) ); setCategory( SystemServices ); setStatus( Active ); setStandardActionsEnabled( false ); m_klipper = new Klipper( this, KSharedConfig::openConfig()); setContextMenu( m_klipper->popup() ); setAssociatedWidget( m_klipper->popup() ); connect( m_klipper->history(), &History::changed, this, &KlipperTray::slotSetToolTipFromHistory); slotSetToolTipFromHistory(); } void KlipperTray::slotSetToolTipFromHistory() { const int TOOLTIP_LENGTH_LIMIT = 200; if (m_klipper->history()->empty()) { setToolTipSubTitle( i18n("Clipboard is empty")); } else { HistoryItemConstPtr top = m_klipper->history()->first(); if (top->text().length() <= TOOLTIP_LENGTH_LIMIT) { setToolTipSubTitle(top->text()); } else { - setToolTipSubTitle(top->text().left(TOOLTIP_LENGTH_LIMIT - 3) + "..." ); + setToolTipSubTitle(top->text().left(TOOLTIP_LENGTH_LIMIT - 3) + QStringLiteral("...") ); } } } diff --git a/klipper/urlgrabber.cpp b/klipper/urlgrabber.cpp index 03a9b8c42..28d8ce0bc 100644 --- a/klipper/urlgrabber.cpp +++ b/klipper/urlgrabber.cpp @@ -1,487 +1,487 @@ /* This file is part of the KDE project Copyright (C) (C) 2000,2001,2002 by Carsten Pfeiffer 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "urlgrabber.h" #include #include "klipper_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "klippersettings.h" #include "clipcommandprocess.h" // TODO: script-interface? #include "history.h" #include "historystringitem.h" URLGrabber::URLGrabber(History* history): m_myCurrentAction(nullptr), m_myMenu(nullptr), m_myPopupKillTimer(new QTimer( this )), m_myPopupKillTimeout(8), m_stripWhiteSpace(true), m_history(history) { m_myPopupKillTimer->setSingleShot( true ); connect(m_myPopupKillTimer, &QTimer::timeout, this, &URLGrabber::slotKillPopupMenu); // testing /* ClipAction *action; action = new ClipAction( "^http:\\/\\/", "Web-URL" ); action->addCommand("kfmclient exec %s", "Open with Konqi", true); action->addCommand("netscape -no-about-splash -remote \"openURL(%s, new-window)\"", "Open with Netscape", true); m_myActions->append( action ); action = new ClipAction( "^mailto:", "Mail-URL" ); action->addCommand("kmail --composer %s", "Launch kmail", true); m_myActions->append( action ); action = new ClipAction( "^\\/.+\\.jpg$", "Jpeg-Image" ); action->addCommand("kuickshow %s", "Launch KuickShow", true); action->addCommand("kview %s", "Launch KView", true); m_myActions->append( action ); */ } URLGrabber::~URLGrabber() { qDeleteAll(m_myActions); m_myActions.clear(); delete m_myMenu; } // // Called from Klipper::slotRepeatAction, i.e. by pressing Ctrl-Alt-R // shortcut. I.e. never from clipboard monitoring // void URLGrabber::invokeAction( HistoryItemConstPtr item ) { m_myClipItem = item; actionMenu( item, false ); } void URLGrabber::setActionList( const ActionList& list ) { qDeleteAll(m_myActions); m_myActions.clear(); m_myActions = list; } void URLGrabber::matchingMimeActions(const QString& clipData) { QUrl url(clipData); KConfigGroup cg(KSharedConfig::openConfig(), "Actions"); if(!cg.readEntry("EnableMagicMimeActions",true)) { //qCDebug(KLIPPER_LOG) << "skipping mime magic due to configuration"; return; } if(!url.isValid()) { //qCDebug(KLIPPER_LOG) << "skipping mime magic due to invalid url"; return; } if(url.isRelative()) { //openinng a relative path will just not work. what path should be used? //qCDebug(KLIPPER_LOG) << "skipping mime magic due to relative url"; return; } if(url.isLocalFile()) { if ( clipData == QLatin1String("//")) { //qCDebug(KLIPPER_LOG) << "skipping mime magic due to C++ comment //"; return; } if(!QFile::exists(url.toLocalFile())) { //qCDebug(KLIPPER_LOG) << "skipping mime magic due to nonexistent localfile"; return; } } // try to figure out if clipData contains a filename QMimeDatabase db; QMimeType mimetype = db.mimeTypeForUrl(url); // let's see if we found some reasonable mimetype. // If we do we'll populate menu with actions for apps // that can handle that mimetype // first: if clipboard contents starts with http, let's assume it's "text/html". // That is even if we've url like "http://www.kde.org/somescript.pl", we'll // still treat that as html page, because determining a mimetype using kio // might take a long time, and i want this function to be quick! if ( ( clipData.startsWith( QLatin1String("http://") ) || clipData.startsWith( QLatin1String("https://") ) ) && mimetype.name() != QLatin1String("text/html") ) { mimetype = db.mimeTypeForName(QStringLiteral("text/html")); } if ( !mimetype.isDefault() ) { KService::List lst = KMimeTypeTrader::self()->query( mimetype.name(), QStringLiteral("Application") ); if ( !lst.isEmpty() ) { ClipAction* action = new ClipAction( QString(), mimetype.comment() ); foreach( const KService::Ptr &service, lst ) { action->addCommand( ClipCommand( QString(), service->name(), true, service->icon(), ClipCommand::IGNORE, service->storageId() ) ); } m_myMatches.append( action ); } } } const ActionList& URLGrabber::matchingActions( const QString& clipData, bool automatically_invoked ) { m_myMatches.clear(); matchingMimeActions(clipData); // now look for matches in custom user actions foreach (ClipAction* action, m_myActions) { if ( action->matches( clipData ) && (action->automatic() || !automatically_invoked) ) { m_myMatches.append( action ); } } return m_myMatches; } void URLGrabber::checkNewData( HistoryItemConstPtr item ) { // qCDebug(KLIPPER_LOG) << "** checking new data: " << clipData; actionMenu( item, true ); // also creates m_myMatches } void URLGrabber::actionMenu( HistoryItemConstPtr item, bool automatically_invoked ) { if (!item) { qWarning("Attempt to invoke URLGrabber without an item"); return; } QString text(item->text()); if (m_stripWhiteSpace) { text = text.trimmed(); } ActionList matchingActionsList = matchingActions( text, automatically_invoked ); if (!matchingActionsList.isEmpty()) { // don't react on blacklisted (e.g. konqi's/netscape's urls) unless the user explicitly asked for it if ( automatically_invoked && isAvoidedWindow() ) { return; } m_myCommandMapper.clear(); m_myPopupKillTimer->stop(); m_myMenu = new QMenu; connect(m_myMenu, &QMenu::triggered, this, &URLGrabber::slotItemSelected); foreach (ClipAction* clipAct, matchingActionsList) { m_myMenu->addSection(QIcon::fromTheme( QStringLiteral("klipper") ), i18n("%1 - Actions For: %2", clipAct->description(), KStringHandler::csqueeze(text, 45))); QList cmdList = clipAct->commands(); int listSize = cmdList.count(); for (int i=0; isetData(id); action->setText(item); if (!command.icon.isEmpty()) action->setIcon(QIcon::fromTheme(command.icon)); m_myCommandMapper.insert(id, qMakePair(clipAct,i)); m_myMenu->addAction(action); } } // only insert this when invoked via clipboard monitoring, not from an // explicit Ctrl-Alt-R if ( automatically_invoked ) { m_myMenu->addSeparator(); QAction *disableAction = new QAction(i18n("Disable This Popup"), this); connect(disableAction, &QAction::triggered, this, &URLGrabber::sigDisablePopup); m_myMenu->addAction(disableAction); } m_myMenu->addSeparator(); QAction *cancelAction = new QAction(QIcon::fromTheme(QStringLiteral("dialog-cancel")), i18n("&Cancel"), this); connect(cancelAction, &QAction::triggered, m_myMenu, &QMenu::hide); m_myMenu->addAction(cancelAction); m_myClipItem = item; if ( m_myPopupKillTimeout > 0 ) m_myPopupKillTimer->start( 1000 * m_myPopupKillTimeout ); emit sigPopup( m_myMenu ); } } void URLGrabber::slotItemSelected(QAction* action) { if (m_myMenu) m_myMenu->hide(); // deleted by the timer or the next action QString id = action->data().toString(); if (id.isEmpty()) { qCDebug(KLIPPER_LOG) << "Klipper: no command associated"; return; } // first is action ptr, second is command index QPair actionCommand = m_myCommandMapper.value(id); if (actionCommand.first) execute(actionCommand.first, actionCommand.second); else qCDebug(KLIPPER_LOG) << "Klipper: cannot find associated action"; } void URLGrabber::execute( const ClipAction* action, int cmdIdx ) const { if (!action) { qCDebug(KLIPPER_LOG) << "Action object is null"; return; } ClipCommand command = action->command(cmdIdx); if ( command.isEnabled ) { QString text(m_myClipItem->text()); if (m_stripWhiteSpace) { text = text.trimmed(); } if( !command.serviceStorageId.isEmpty()) { KService::Ptr service = KService::serviceByStorageId( command.serviceStorageId ); KRun::runApplication( *service, QList< QUrl >() << QUrl( text ), nullptr ); } else { ClipCommandProcess* proc = new ClipCommandProcess(*action, command, text, m_history, m_myClipItem); if (proc->program().isEmpty()) { delete proc; proc = nullptr; } else { proc->start(); } } } } void URLGrabber::loadSettings() { m_stripWhiteSpace = KlipperSettings::stripWhiteSpace(); m_myAvoidWindows = KlipperSettings::noActionsForWM_CLASS(); m_myPopupKillTimeout = KlipperSettings::timeoutForActionPopups(); qDeleteAll(m_myActions); m_myActions.clear(); KConfigGroup cg(KSharedConfig::openConfig(), "General"); int num = cg.readEntry("Number of Actions", 0); QString group; for ( int i = 0; i < num; i++ ) { group = QStringLiteral("Action_%1").arg( i ); m_myActions.append( new ClipAction( KSharedConfig::openConfig(), group ) ); } } void URLGrabber::saveSettings() const { KConfigGroup cg(KSharedConfig::openConfig(), "General"); cg.writeEntry( "Number of Actions", m_myActions.count() ); int i = 0; QString group; foreach (ClipAction* action, m_myActions) { group = QStringLiteral("Action_%1").arg( i ); action->save( KSharedConfig::openConfig(), group ); ++i; } KlipperSettings::setNoActionsForWM_CLASS(m_myAvoidWindows); } // find out whether the active window's WM_CLASS is in our avoid-list bool URLGrabber::isAvoidedWindow() const { const WId active = KWindowSystem::activeWindow(); if (!active) { return false; } KWindowInfo info(active, NET::Properties(), NET::WM2WindowClass); - return m_myAvoidWindows.contains(info.windowClassName()); + return m_myAvoidWindows.contains(QString::fromLatin1(info.windowClassName())); } void URLGrabber::slotKillPopupMenu() { if ( m_myMenu && m_myMenu->isVisible() ) { if ( m_myMenu->geometry().contains( QCursor::pos() ) && m_myPopupKillTimeout > 0 ) { m_myPopupKillTimer->start( 1000 * m_myPopupKillTimeout ); return; } } if ( m_myMenu ) { m_myMenu->deleteLater(); m_myMenu = nullptr; } } /////////////////////////////////////////////////////////////////////////// //////// ClipCommand::ClipCommand(const QString&_command, const QString& _description, bool _isEnabled, const QString& _icon, Output _output, const QString& _serviceStorageId) : command(_command), description(_description), isEnabled(_isEnabled), output(_output), serviceStorageId( _serviceStorageId) { if (!_icon.isEmpty()) icon = _icon; else { // try to find suitable icon QString appName = command.section( QLatin1Char(' '), 0, 0 ); if ( !appName.isEmpty() ) { QPixmap iconPix = KIconLoader::global()->loadIcon( appName, KIconLoader::Small, 0, KIconLoader::DefaultState, QStringList(), nullptr, true /* canReturnNull */ ); if ( !iconPix.isNull() ) icon = appName; else icon.clear(); } } } ClipAction::ClipAction( const QString& regExp, const QString& description, bool automatic ) : m_myRegExp( regExp ), m_myDescription( description ), m_automatic(automatic) { } ClipAction::ClipAction( KSharedConfigPtr kc, const QString& group ) : m_myRegExp( kc->group(group).readEntry("Regexp") ), m_myDescription (kc->group(group).readEntry("Description") ), m_automatic(kc->group(group).readEntry("Automatic", QVariant(true)).toBool() ) { KConfigGroup cg(kc, group); int num = cg.readEntry( "Number of commands", 0 ); // read the commands for ( int i = 0; i < num; i++ ) { - QString _group = group + "/Command_%1"; + QString _group = group + QStringLiteral("/Command_%1"); KConfigGroup _cg(kc, _group.arg(i)); addCommand( ClipCommand(_cg.readPathEntry( "Commandline", QString() ), _cg.readEntry( "Description" ), // i18n'ed _cg.readEntry( "Enabled" , false), _cg.readEntry( "Icon"), static_cast(_cg.readEntry( "Output", QVariant(ClipCommand::IGNORE)).toInt()))); } } ClipAction::~ClipAction() { m_myCommands.clear(); } void ClipAction::addCommand( const ClipCommand& cmd ) { if ( cmd.command.isEmpty() && cmd.serviceStorageId.isEmpty() ) return; m_myCommands.append( cmd ); } void ClipAction::replaceCommand( int idx, const ClipCommand& cmd ) { if ( idx < 0 || idx >= m_myCommands.count() ) { qCDebug(KLIPPER_LOG) << "wrong command index given"; return; } m_myCommands.replace(idx, cmd); } // precondition: we're in the correct action's group of the KConfig object void ClipAction::save( KSharedConfigPtr kc, const QString& group ) const { KConfigGroup cg(kc, group); cg.writeEntry( "Description", description() ); cg.writeEntry( "Regexp", regExp() ); cg.writeEntry( "Number of commands", m_myCommands.count() ); cg.writeEntry( "Automatic", automatic() ); int i=0; // now iterate over all commands of this action foreach (const ClipCommand& cmd, m_myCommands) { - QString _group = group + "/Command_%1"; + QString _group = group + QStringLiteral("/Command_%1"); KConfigGroup cg(kc, _group.arg(i)); cg.writePathEntry( "Commandline", cmd.command ); cg.writeEntry( "Description", cmd.description ); cg.writeEntry( "Enabled", cmd.isEnabled ); cg.writeEntry( "Icon", cmd.icon ); cg.writeEntry( "Output", static_cast(cmd.output) ); ++i; } } diff --git a/ksmserver/CMakeLists.txt b/ksmserver/CMakeLists.txt index d2e4e9685..178f3ba65 100644 --- a/ksmserver/CMakeLists.txt +++ b/ksmserver/CMakeLists.txt @@ -1,95 +1,99 @@ add_definitions(-DTRANSLATION_DOMAIN=\"ksmserver\") include_directories(${PHONON_INCLUDE_DIR}) check_library_exists(ICE _IceTransNoListen "" HAVE__ICETRANSNOLISTEN) configure_file(config-ksmserver.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ksmserver.h) +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) + include_directories(${CMAKE_CURRENT_BINARY_DIR}) add_subdirectory(logout-greeter) add_subdirectory(switchuser-greeter) if(BUILD_TESTING) add_subdirectory(tests) endif() ########### next target ############### set(ksmserver_KDEINIT_SRCS main.cpp server.cpp legacy.cpp startup.cpp autostart.cpp shutdown.cpp client.cpp ) ecm_qt_declare_logging_category(ksmserver_KDEINIT_SRCS HEADER ksmserver_debug.h IDENTIFIER KSMSERVER CATEGORY_NAME org.kde.kf5.ksmserver) set(kcminit_adaptor ${plasma-workspace_SOURCE_DIR}/startkde/kcminit/main.h) set(kcminit_xml ${CMAKE_CURRENT_BINARY_DIR}/org.kde.KCMinit.xml) qt5_generate_dbus_interface( ${kcminit_adaptor} ${kcminit_xml} ) qt5_add_dbus_interface( ksmserver_KDEINIT_SRCS ${kcminit_xml} kcminit_interface ) # FIXME: This is actually not disabled any more because OrgKDEKlauncherInterface isn't provided # otherwise. # # This is actually now disabled, because OrgKDEKlauncherInterface is also provided # # by kdecore, it is not autogenerated and is not binary compatible with a currently # # generated version, thus at certain circumstances leading to strange crashes. # # This should be fixed for KDE5. # # KLauchner.xml is installed by kdelibs, so it is in KDE4_DBUS_INTERFACES_DIR set(klauncher_xml ${KINIT_DBUS_INTERFACES_DIR}/kf5_org.kde.KLauncher.xml) qt5_add_dbus_interface( ksmserver_KDEINIT_SRCS ${klauncher_xml} klauncher_interface ) qt5_add_dbus_adaptor( ksmserver_KDEINIT_SRCS org.kde.KSMServerInterface.xml server.h KSMServer ) kf5_add_kdeinit_executable( ksmserver ${ksmserver_KDEINIT_SRCS}) set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KSMServerDBusInterface") configure_package_config_file(KSMServerDBusInterfaceConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/KSMServerDBusInterfaceConfig.cmake PATH_VARS KDE_INSTALL_DBUSINTERFACEDIR INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}) target_link_libraries(kdeinit_ksmserver PW::KScreenLocker PW::KWorkspace KF5::XmlGui KF5::GlobalAccel KF5::KIOCore KF5::KIOWidgets ${X11_LIBRARIES} ${X11_Xrender_LIB} Qt5::X11Extras KF5::Solid Qt5::Quick KF5::Declarative KF5::DBusAddons KF5::Package KF5::KDELibs4Support # Solid/PowerManagement ${PHONON_LIBRARIES} Qt5::Concurrent ) install(TARGETS kdeinit_ksmserver ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(TARGETS ksmserver ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/KSMServerDBusInterfaceConfig.cmake DESTINATION ${CMAKECONFIG_INSTALL_DIR}) ########### next target ############### 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}) ########### install files ############### install( FILES org.kde.KSMServerInterface.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR}) diff --git a/ksmserver/main.cpp b/ksmserver/main.cpp index 4b3fcf0be..93db1168d 100644 --- a/ksmserver/main.cpp +++ b/ksmserver/main.cpp @@ -1,355 +1,355 @@ /***************************************************************** 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 #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:") + - "\n\n "; + 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); //TODO: should we still use this? // if( !QDBusConnection::sessionBus().interface()-> // registerService( QStringLiteral( "org.kde.ksmserver" ), // QDBusConnectionInterface::DontQueueService ) ) // { // qCWarning(KSMSERVER, "Could not register with D-BUS. Aborting."); // return 1; // } 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); // 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 ) 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(); 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/startup.cpp b/ksmserver/startup.cpp index a8dc11a20..064b5f36b 100644 --- a/ksmserver/startup.cpp +++ b/ksmserver/startup.cpp @@ -1,711 +1,711 @@ /***************************************************************** 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 #include #include #include // HAVE_LIMITS_H #include #include #include #include #include #include #ifdef HAVE_SYS_TIME_H #include #endif #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 "global.h" #include "server.h" #include "client.h" #include //#include "kdesktop_interface.h" #include #include #include "kcminit_interface.h" //#define KSMSERVER_STARTUP_DEBUG1 #ifdef KSMSERVER_STARTUP_DEBUG1 static QTime t; #endif // Put the notification in its own thread as it can happen that // PulseAudio will start initializing with this, so let's not // block the main thread with waiting for PulseAudio to start class NotificationThread : public QThread { Q_OBJECT void run() override { // We cannot parent to the thread itself so let's create // a QObject on the stack and parent everythign to it QObject parent; KNotifyConfig notifyConfig(QStringLiteral("plasma_workspace"), QList< QPair >(), QStringLiteral("startkde")); const QString action = notifyConfig.readEntry(QStringLiteral("Action")); - if (action.isEmpty() || !action.split('|').contains(QStringLiteral("Sound"))) { + if (action.isEmpty() || !action.split(QLatin1Char('|')).contains(QStringLiteral("Sound"))) { // no startup sound configured return; } Phonon::AudioOutput *m_audioOutput = new Phonon::AudioOutput(Phonon::NotificationCategory, &parent); QString soundFilename = notifyConfig.readEntry(QStringLiteral("Sound")); if (soundFilename.isEmpty()) { qCWarning(KSMSERVER) << "Audio notification requested, but no sound file provided in notifyrc file, aborting audio notification"; return; } QUrl soundURL; const auto dataLocations = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); for (const QString &dataLocation: dataLocations) { soundURL = QUrl::fromUserInput(soundFilename, dataLocation + QStringLiteral("/sounds"), QUrl::AssumeLocalFile); if (soundURL.isLocalFile() && QFile::exists(soundURL.toLocalFile())) { break; } else if (!soundURL.isLocalFile() && soundURL.isValid()) { break; } soundURL.clear(); } if (soundURL.isEmpty()) { qCWarning(KSMSERVER) << "Audio notification requested, but sound file from notifyrc file was not found, aborting audio notification"; return; } Phonon::MediaObject *m = new Phonon::MediaObject(&parent); connect(m, &Phonon::MediaObject::finished, this, &NotificationThread::quit); Phonon::createPath(m, m_audioOutput); m->setCurrentSource(soundURL); m->play(); exec(); } }; /*! 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; upAndRunning( QStringLiteral( "ksmserver" ) ); // 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 ); } /*! 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(); upAndRunning( QStringLiteral( "ksmserver" ) ); launchWM( QList< QStringList >() << wmCommands ); } 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())); } autoStart0(); } void KSMServer::clientSetProgram( KSMClient* client ) { if( client->program() == wm ) autoStart0(); } 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; } } void KSMServer::autoStart0() { if( state != LaunchingWM ) return; if( !checkStartupSuspend()) return; state = AutoStart0; #ifdef KSMSERVER_STARTUP_DEBUG1 qCDebug(KSMSERVER) << t.elapsed(); #endif autoStart(0); } void KSMServer::autoStart0Done() { if( state != AutoStart0 ) return; if( !checkStartupSuspend()) return; qCDebug(KSMSERVER) << "Autostart 0 done"; #ifdef KSMSERVER_STARTUP_DEBUG1 qCDebug(KSMSERVER) << t.elapsed(); #endif state = KcmInitPhase1; kcminitSignals = new QDBusInterface( QStringLiteral( "org.kde.kcminit"), QStringLiteral( "/kcminit" ), QStringLiteral( "org.kde.KCMInit" ), QDBusConnection::sessionBus(), this ); if( !kcminitSignals->isValid()) { qCWarning(KSMSERVER) << "kcminit not running? If we are running with mobile profile or in another platform other than X11 this is normal."; delete kcminitSignals; kcminitSignals = nullptr; QTimer::singleShot(0, this, &KSMServer::kcmPhase1Done); return; } connect( kcminitSignals, SIGNAL(phase1Done()), SLOT(kcmPhase1Done())); QTimer::singleShot( 10000, this, &KSMServer::kcmPhase1Timeout); // protection org::kde::KCMInit kcminit(QStringLiteral("org.kde.kcminit"), QStringLiteral("/kcminit"), QDBusConnection::sessionBus()); kcminit.runPhase1(); } void KSMServer::kcmPhase1Done() { if( state != KcmInitPhase1 ) return; qCDebug(KSMSERVER) << "Kcminit phase 1 done"; if (kcminitSignals) { disconnect( kcminitSignals, SIGNAL(phase1Done()), this, SLOT(kcmPhase1Done())); } autoStart1(); } void KSMServer::kcmPhase1Timeout() { if( state != KcmInitPhase1 ) return; qCDebug(KSMSERVER) << "Kcminit phase 1 timeout"; kcmPhase1Done(); } void KSMServer::autoStart1() { if( state != KcmInitPhase1 ) return; state = AutoStart1; #ifdef KSMSERVER_STARTUP_DEBUG1 qCDebug(KSMSERVER)<< t.elapsed(); #endif autoStart(1); } void KSMServer::autoStart1Done() { if( state != AutoStart1 ) return; if( !checkStartupSuspend()) return; qCDebug(KSMSERVER) << "Autostart 1 done"; setupShortcuts(); // done only here, because it needs kglobalaccel :-/ lastAppStarted = 0; lastIdStarted.clear(); state = Restoring; #ifdef KSMSERVER_STARTUP_DEBUG1 qCDebug(KSMSERVER)<< t.elapsed(); #endif if( defaultSession()) { autoStart2(); return; } tryRestoreNext(); } void KSMServer::clientRegistered( const char* previousId ) { if ( previousId && lastIdStarted == QString::fromLocal8Bit( previousId ) ) tryRestoreNext(); } void KSMServer::tryRestoreNext() { if( state != Restoring && state != RestoringSubSession ) return; restoreTimer.stop(); startupSuspendTimeoutTimer.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) autoStart2(); else { //subsession state = Idle; emit subSessionOpened(); } } void KSMServer::autoStart2() { if( state != Restoring ) return; if( !checkStartupSuspend()) return; state = FinishingStartup; #ifdef KSMSERVER_STARTUP_DEBUG1 qCDebug(KSMSERVER)<< t.elapsed(); #endif waitAutoStart2 = true; waitKcmInit2 = true; autoStart(2); QDBusInterface kded( QStringLiteral( "org.kde.kded5" ), QStringLiteral( "/kded" ), QStringLiteral( "org.kde.kded5" ) ); auto pending = kded.asyncCall( QStringLiteral( "loadSecondPhase" ) ); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending, this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, &KSMServer::secondKDEDPhaseLoaded); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher, &QObject::deleteLater); runUserAutostart(); if (kcminitSignals) { connect( kcminitSignals, SIGNAL(phase2Done()), SLOT(kcmPhase2Done())); QTimer::singleShot( 10000, this, &KSMServer::kcmPhase2Timeout); // protection org::kde::KCMInit kcminit(QStringLiteral("org.kde.kcminit"), QStringLiteral("/kcminit"), QDBusConnection::sessionBus()); kcminit.runPhase2(); } else { QTimer::singleShot(0, this, &KSMServer::kcmPhase2Done); } } void KSMServer::secondKDEDPhaseLoaded() { #ifdef KSMSERVER_STARTUP_DEBUG1 qCDebug(KSMSERVER)<< "kded" << t.elapsed(); #endif if( !defaultSession()) restoreLegacySession(KSharedConfig::openConfig().data()); qCDebug(KSMSERVER) << "Starting notification thread"; NotificationThread *loginSound = new NotificationThread(); // Delete the thread when finished connect(loginSound, &NotificationThread::finished, loginSound, &NotificationThread::deleteLater); loginSound->start(); } void KSMServer::runUserAutostart() { // Now let's execute the scripts in the KDE-specific autostart-scripts folder. const QString autostartFolder = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QDir::separator() + QStringLiteral("autostart-scripts"); QDir dir(autostartFolder); if (!dir.exists()) { // Create dir in all cases, so that users can find it :-) dir.mkpath(QStringLiteral(".")); if (!migrateKDE4Autostart(autostartFolder)) { return; } } const QStringList entries = dir.entryList(QDir::Files); foreach (const QString &file, entries) { // Don't execute backup files if (!file.endsWith(QLatin1Char('~')) && !file.endsWith(QStringLiteral(".bak")) && (file[0] != QLatin1Char('%') || !file.endsWith(QLatin1Char('%'))) && (file[0] != QLatin1Char('#') || !file.endsWith(QLatin1Char('#')))) { const QString fullPath = dir.absolutePath() + QLatin1Char('/') + file; qCInfo(KSMSERVER) << "Starting autostart script " << fullPath; auto p = new KProcess; //deleted in onFinished lambda p->setProgram(fullPath); p->start(); connect(p, static_cast(&QProcess::finished), [p](int exitCode) { qCInfo(KSMSERVER) << "autostart script" << p->program() << "finished with exit code " << exitCode; p->deleteLater(); }); } } } bool KSMServer::migrateKDE4Autostart(const QString &autostartFolder) { // Migrate user autostart from kde4 Kdelibs4Migration migration; if (!migration.kdeHomeFound()) { return false; } // KDEHOME/Autostart was the default value for KGlobalSettings::autostart() QString oldAutostart = migration.kdeHome() + QStringLiteral("/Autostart"); // That path could be customized in kdeglobals const QString oldKdeGlobals = migration.locateLocal("config", QStringLiteral("kdeglobals")); if (!oldKdeGlobals.isEmpty()) { oldAutostart = KConfig(oldKdeGlobals).group("Paths").readEntry("Autostart", oldAutostart); } const QDir oldFolder(oldAutostart); qCDebug(KSMSERVER) << "Copying autostart files from" << oldFolder.path(); const QStringList entries = oldFolder.entryList(QDir::Files); foreach (const QString &file, entries) { const QString src = oldFolder.absolutePath() + QLatin1Char('/') + file; const QString dest = autostartFolder + QLatin1Char('/') + file; QFileInfo info(src); bool success; if (info.isSymLink()) { // This will only work with absolute symlink targets success = QFile::link(info.symLinkTarget(), dest); } else { success = QFile::copy(src, dest); } if (!success) { qCWarning(KSMSERVER) << "Error copying" << src << "to" << dest; } } return true; } void KSMServer::autoStart2Done() { if( state != FinishingStartup ) return; qCDebug(KSMSERVER) << "Autostart 2 done"; waitAutoStart2 = false; finishStartup(); } void KSMServer::kcmPhase2Done() { if( state != FinishingStartup ) return; qCDebug(KSMSERVER) << "Kcminit phase 2 done"; if (kcminitSignals) { disconnect( kcminitSignals, SIGNAL(phase2Done()), this, SLOT(kcmPhase2Done())); delete kcminitSignals; kcminitSignals = nullptr; } waitKcmInit2 = false; finishStartup(); } void KSMServer::kcmPhase2Timeout() { if( !waitKcmInit2 ) return; qCDebug(KSMSERVER) << "Kcminit phase 2 timeout"; kcmPhase2Done(); } void KSMServer::finishStartup() { if( state != FinishingStartup ) return; if( waitAutoStart2 || waitKcmInit2 ) return; upAndRunning( QStringLiteral( "ready" ) ); #ifdef KSMSERVER_STARTUP_DEBUG1 qCDebug(KSMSERVER)<< t.elapsed(); #endif state = Idle; setupXIOErrorHandler(); // From now on handle X errors as normal shutdown. } bool KSMServer::checkStartupSuspend() { if( startupSuspendCount.isEmpty()) return true; // wait for the phase to finish if( !startupSuspendTimeoutTimer.isActive()) { startupSuspendTimeoutTimer.setSingleShot( true ); startupSuspendTimeoutTimer.start( 10000 ); } return false; } void KSMServer::suspendStartup( const QString &app ) { if( !startupSuspendCount.contains( app )) startupSuspendCount[ app ] = 0; ++startupSuspendCount[ app ]; } void KSMServer::resumeStartup( const QString &app ) { if( !startupSuspendCount.contains( app )) return; if( --startupSuspendCount[ app ] == 0 ) { startupSuspendCount.remove( app ); if( startupSuspendCount.isEmpty() && startupSuspendTimeoutTimer.isActive()) { startupSuspendTimeoutTimer.stop(); resumeStartupInternal(); } } } void KSMServer::startupSuspendTimeout() { qCDebug(KSMSERVER) << "Startup suspend timeout:" << state; resumeStartupInternal(); } void KSMServer::resumeStartupInternal() { startupSuspendCount.clear(); switch( state ) { case LaunchingWM: autoStart0(); break; case AutoStart0: autoStart0Done(); break; case AutoStart1: autoStart1Done(); break; case Restoring: autoStart2(); break; default: qCWarning(KSMSERVER) << "Unknown resume startup state" ; break; } } void KSMServer::upAndRunning( const QString& msg ) { QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"), QStringLiteral("/KSplash"), QStringLiteral("org.kde.KSplash"), QStringLiteral("setStage")); ksplashProgressMessage.setArguments(QList() << msg); QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); } 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::autoStart(int phase) { if (m_autoStart.phase() >= phase) { return; } m_autoStart.setPhase(phase); if (phase == 0) { m_autoStart.loadAutoStartList(); } QTimer::singleShot(0, this, &KSMServer::slotAutoStart); } void KSMServer::slotAutoStart() { do { QString serviceName = m_autoStart.startService(); if (serviceName.isEmpty()) { // Done if (!m_autoStart.phaseDone()) { m_autoStart.setPhaseDone(); switch (m_autoStart.phase()) { case 0: autoStart0Done(); break; case 1: autoStart1Done(); break; case 2: autoStart2Done(); break; } } return; } KService service(serviceName); auto arguments = KIO::DesktopExecParser(service, QList()).resultingArguments(); if (arguments.isEmpty()) { qCWarning(KSMSERVER) << "failed to parse" << serviceName << "for autostart"; continue; } qCInfo(KSMSERVER) << "Starting autostart service " << serviceName << arguments; auto program = arguments.takeFirst(); if (!QProcess::startDetached(program, arguments)) qCWarning(KSMSERVER) << "could not start" << serviceName << ":" << program << arguments; } while (true); // Loop till we find a service that we can start. } #include "startup.moc" diff --git a/ksmserver/switchuserdialog.cpp b/ksmserver/switchuserdialog.cpp index d1f6e69e6..01a0faf82 100644 --- a/ksmserver/switchuserdialog.cpp +++ b/ksmserver/switchuserdialog.cpp @@ -1,171 +1,171 @@ /* * Copyright 2015 Kai Uwe Broulik * * 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) 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 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "switchuserdialog.h" #include "ksmserver_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KSMSwitchUserDialog::KSMSwitchUserDialog(KDisplayManager *dm, KWayland::Client::PlasmaShell *plasmaShell, QWindow *parent) : QQuickView(parent) , m_displayManager(dm) , m_waylandPlasmaShell(plasmaShell) { setClearBeforeRendering(true); setColor(QColor(Qt::transparent)); setFlags(Qt::FramelessWindowHint | Qt::BypassWindowManagerHint); setResizeMode(QQuickView::SizeRootObjectToView); QPoint globalPosition(QCursor::pos()); foreach (QScreen *s, QGuiApplication::screens()) { if (s->geometry().contains(globalPosition)) { setScreen(s); break; } } // Qt doesn't set this on unmanaged windows //FIXME: or does it? if (KWindowSystem::isPlatformX11()) { XChangeProperty( QX11Info::display(), winId(), XInternAtom( QX11Info::display(), "WM_WINDOW_ROLE", False ), XA_STRING, 8, PropModeReplace, (unsigned char *)"logoutdialog", strlen( "logoutdialog" )); XClassHint classHint; classHint.res_name = const_cast("ksmserver"); classHint.res_class = const_cast("ksmserver"); XSetClassHint(QX11Info::display(), winId(), &classHint); } KDeclarative::KDeclarative kdeclarative; kdeclarative.setDeclarativeEngine(engine()); kdeclarative.setupBindings(); } void KSMSwitchUserDialog::init() { rootContext()->setContextProperty(QStringLiteral("screenGeometry"), screen()->geometry()); - KPackage::Package package = KPackage::PackageLoader::self()->loadPackage("Plasma/LookAndFeel"); - KConfigGroup cg(KSharedConfig::openConfig("kdeglobals"), "KDE"); + KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel")); + KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), QStringLiteral("KDE")); const QString packageName = cg.readEntry("LookAndFeelPackage", QString()); if (!packageName.isEmpty()) { package.setPath(packageName); } const QString fileName = package.filePath("userswitchermainscript"); if (QFile::exists(fileName)) { setSource(QUrl::fromLocalFile(fileName)); } else { qCWarning(KSMSERVER) << "Couldn't find a theme for the Switch User dialog" << fileName; return; } if (!errors().isEmpty()) { qCWarning(KSMSERVER) << errors(); } connect(rootObject(), SIGNAL(dismissed()), this, SIGNAL(dismissed())); connect(rootObject(), SIGNAL(ungrab()), this, SLOT(ungrab())); connect(screen(), &QScreen::geometryChanged, this, [this] { setGeometry(screen()->geometry()); }); QQuickView::show(); requestActivate(); KWindowSystem::setState(winId(), NET::SkipTaskbar|NET::SkipPager); // in case you change this make sure to adjust ungrab() also setKeyboardGrabEnabled(true); } bool KSMSwitchUserDialog::event(QEvent *e) { if (e->type() == QEvent::PlatformSurface) { switch (static_cast(e)->surfaceEventType()) { case QPlatformSurfaceEvent::SurfaceCreated: setupWaylandIntegration(); KWindowEffects::enableBlurBehind(winId(), true); break; case QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed: delete m_shellSurface; m_shellSurface = nullptr; break; } } return QQuickView::event(e); } void KSMSwitchUserDialog::setupWaylandIntegration() { if (m_shellSurface) { // already setup return; } using namespace KWayland::Client; if (!m_waylandPlasmaShell) { return; } Surface *s = Surface::fromWindow(this); if (!s) { return; } m_shellSurface = m_waylandPlasmaShell->createSurface(s, this); // TODO: set a proper window type to indicate to KWin that this is the logout dialog // maybe we need a dedicated type for it? m_shellSurface->setPosition(geometry().topLeft()); } void KSMSwitchUserDialog::ungrab() { // Allow the screenlocker to grab them immediately setKeyboardGrabEnabled(false); setMouseGrabEnabled(false); } diff --git a/plasma-windowed/CMakeLists.txt b/plasma-windowed/CMakeLists.txt index 867cdc7e9..4cf965215 100644 --- a/plasma-windowed/CMakeLists.txt +++ b/plasma-windowed/CMakeLists.txt @@ -1,25 +1,29 @@ +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(plasmawindowed-app_SRCS plasmawindowedcorona.cpp plasmawindowedview.cpp main.cpp ) add_executable(plasmawindowed ${plasmawindowed-app_SRCS}) target_link_libraries( plasmawindowed Qt5::Widgets Qt5::Quick Qt5::Qml KF5::I18n KF5::IconThemes KF5::XmlGui KF5::PlasmaQuick KF5::Plasma KF5::DBusAddons KF5::Notifications ) install(TARGETS plasmawindowed ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) #even if hidden, the desktop file is needed anyways for kdbusservice::unique install(FILES plasma-windowed.desktop DESTINATION ${KDE_INSTALL_APPDIR}) diff --git a/plasma-windowed/main.cpp b/plasma-windowed/main.cpp index abc126e09..985c5c7e4 100644 --- a/plasma-windowed/main.cpp +++ b/plasma-windowed/main.cpp @@ -1,74 +1,74 @@ /* * Copyright 2014 Bhushan Shah * Copyright 2014 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) 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 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see */ #include #include #include #include #include #include "plasmawindowedview.h" #include "plasmawindowedcorona.h" static const char version[] = "1.0"; int main(int argc, char **argv) { QQuickWindow::setDefaultAlphaBuffer(true); QApplication app(argc, argv); - app.setApplicationVersion(version); + app.setApplicationVersion(QLatin1String(version)); app.setOrganizationDomain(QStringLiteral("kde.org")); KDBusService service(KDBusService::Unique); QCommandLineParser parser; parser.setApplicationDescription(i18n("Plasma Windowed")); parser.addOption(QCommandLineOption(QStringLiteral("statusnotifier"), i18n("Makes the plasmoid stay alive in the Notification Area, even when the window is closed."))); parser.addPositionalArgument(QStringLiteral("applet"), i18n("The applet to open.")); parser.addPositionalArgument(QStringLiteral("args"), i18n("Arguments to pass to the plasmoid."), QStringLiteral("[args...]")); parser.addVersionOption(); parser.addHelpOption(); parser.process(app); if (parser.positionalArguments().isEmpty()) { parser.showHelp(1); } PlasmaWindowedCorona *corona = new PlasmaWindowedCorona(); const QStringList arguments = parser.positionalArguments(); QVariantList args; QStringList::const_iterator constIterator = arguments.constBegin() + 1; for (; constIterator != arguments.constEnd(); ++constIterator) { args << (*constIterator); } corona->setHasStatusNotifier(parser.isSet(QStringLiteral("statusnotifier"))); corona->loadApplet(arguments.first(), args); QObject::connect(&service, &KDBusService::activateRequested, corona, &PlasmaWindowedCorona::activateRequested); const int ret = app.exec(); delete corona; return ret; } diff --git a/plasmacalendarintegration/CMakeLists.txt b/plasmacalendarintegration/CMakeLists.txt index e881a62d4..8556aaba2 100644 --- a/plasmacalendarintegration/CMakeLists.txt +++ b/plasmacalendarintegration/CMakeLists.txt @@ -1,20 +1,24 @@ +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(holidays-plugin_SRCS holidaysevents.cpp ) add_library(holidaysevents MODULE ${holidays-plugin_SRCS}) target_link_libraries(holidaysevents Qt5::Core KF5::ConfigCore KF5::Holidays KF5::CalendarEvents ) install (TARGETS holidaysevents DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasmacalendarplugins ) install (FILES HolidaysConfig.qml DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasmacalendarplugins/holidays ) add_subdirectory(qmlhelper) diff --git a/plasmacalendarintegration/holidaysevents.cpp b/plasmacalendarintegration/holidaysevents.cpp index 62dcd836a..675ff8d10 100644 --- a/plasmacalendarintegration/holidaysevents.cpp +++ b/plasmacalendarintegration/holidaysevents.cpp @@ -1,85 +1,85 @@ /* Copyright (C) 2015 Martin Klapetek 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 "holidaysevents.h" #include #include HolidaysEventsPlugin::HolidaysEventsPlugin(QObject *parent) : CalendarEvents::CalendarEventsPlugin(parent) { - KSharedConfig::Ptr m_config = KSharedConfig::openConfig("plasma_calendar_holiday_regions"); + KSharedConfig::Ptr m_config = KSharedConfig::openConfig(QStringLiteral("plasma_calendar_holiday_regions")); const KConfigGroup regionsConfig = m_config->group("General"); QStringList regionCodes = regionsConfig.readEntry("selectedRegions", QStringList()); regionCodes.removeDuplicates(); // If the config does not have any region stored // add the default one if (regionCodes.isEmpty()) { regionCodes << KHolidays::HolidayRegion::defaultRegionCode(); } Q_FOREACH (const QString ®ion, regionCodes) { m_regions << new KHolidays::HolidayRegion(region); } } HolidaysEventsPlugin::~HolidaysEventsPlugin() { qDeleteAll(m_regions); } void HolidaysEventsPlugin::loadEventsForDateRange(const QDate &startDate, const QDate &endDate) { if (m_lastStartDate == startDate && m_lastEndDate == endDate) { Q_EMIT dataReady(m_lastData); return; } m_lastData.clear(); QMultiHash data; Q_FOREACH (KHolidays::HolidayRegion *region, m_regions) { KHolidays::Holiday::List holidays = region->holidays(startDate, endDate); Q_FOREACH (const KHolidays::Holiday &holiday, holidays) { CalendarEvents::EventData eventData; eventData.setStartDateTime(QDateTime(holiday.observedStartDate())); eventData.setEndDateTime(QDateTime(holiday.observedEndDate())); eventData.setIsAllDay(true); eventData.setTitle(holiday.name()); eventData.setEventType(CalendarEvents::EventData::Holiday); eventData.setIsMinor(false); // make sure to add events spanning multiple days to all of them for (QDate d = holiday.observedStartDate(); d <= holiday.observedEndDate(); d = d.addDays(1)) { data.insert(d, eventData); } } } m_lastStartDate = startDate; m_lastEndDate = endDate; m_lastData = data; qDebug() << data.size(); Q_EMIT dataReady(data); } diff --git a/plasmacalendarintegration/qmlhelper/holidayeventshelperplugin.cpp b/plasmacalendarintegration/qmlhelper/holidayeventshelperplugin.cpp index 9ae11d7bf..4efb79905 100644 --- a/plasmacalendarintegration/qmlhelper/holidayeventshelperplugin.cpp +++ b/plasmacalendarintegration/qmlhelper/holidayeventshelperplugin.cpp @@ -1,80 +1,80 @@ /* Copyright (c) 2015 Martin Klapetek 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 "holidayeventshelperplugin.h" #include #include #include #include class QmlConfigHelper : public QObject { Q_OBJECT Q_PROPERTY(QStringList selectedRegions READ selectedRegions NOTIFY selectedRegionsChanged) public: explicit QmlConfigHelper(QObject *parent = nullptr) : QObject(parent) { - KSharedConfig::Ptr config = KSharedConfig::openConfig("plasma_calendar_holiday_regions"); + KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("plasma_calendar_holiday_regions")); m_configGroup = config->group("General"); m_regions = m_configGroup.readEntry("selectedRegions", QStringList()); } QStringList selectedRegions() const { return m_regions; } Q_INVOKABLE void saveConfig() { m_configGroup.writeEntry("selectedRegions", m_regions); m_configGroup.sync(); } Q_INVOKABLE void addRegion(const QString ®ion) { if (!m_regions.contains(region)) { m_regions.append(region); Q_EMIT selectedRegionsChanged(); } } Q_INVOKABLE void removeRegion(const QString ®ion) { if (m_regions.removeOne(region)) { Q_EMIT selectedRegionsChanged(); } } Q_SIGNALS: void selectedRegionsChanged(); private: QStringList m_regions; KConfigGroup m_configGroup; }; void HolidayEventsHelperPlugin::registerTypes(const char* uri) { qmlRegisterType(uri, 1, 0, "QmlConfigHelper"); } #include "holidayeventshelperplugin.moc" diff --git a/shell/containmentconfigview.cpp b/shell/containmentconfigview.cpp index b50dd73db..31e7ccbe8 100644 --- a/shell/containmentconfigview.cpp +++ b/shell/containmentconfigview.cpp @@ -1,217 +1,217 @@ /* * Copyright 2013 Marco Martin * * 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. */ #include "currentcontainmentactionsmodel.h" #include "containmentconfigview.h" #include "plasmaquick/configmodel.h" #include "shellcorona.h" #include "config-workspace.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //////////////////////////////ContainmentConfigView ContainmentConfigView::ContainmentConfigView(Plasma::Containment *cont, QWindow *parent) : ConfigView(cont, parent), m_containment(cont) { qmlRegisterType(); rootContext()->setContextProperty(QStringLiteral("configDialog"), this); setCurrentWallpaper(cont->containment()->wallpaper()); KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Wallpaper")); pkg.setPath(m_containment->wallpaper()); KConfigGroup cfg = m_containment->config(); cfg = KConfigGroup(&cfg, "Wallpaper"); syncWallpaperObjects(); } ContainmentConfigView::~ContainmentConfigView() { } void ContainmentConfigView::init() { setSource(m_containment->corona()->kPackage().fileUrl("containmentconfigurationui")); } PlasmaQuick::ConfigModel *ContainmentConfigView::containmentActionConfigModel() { if (!m_containmentActionConfigModel) { m_containmentActionConfigModel = new PlasmaQuick::ConfigModel(this); KPluginInfo::List actions = Plasma::PluginLoader::self()->listContainmentActionsInfo(QString()); KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Generic")); foreach (const KPluginInfo &info, actions) { - pkg.setDefaultPackageRoot(QStandardPaths::locate(QStandardPaths::GenericDataLocation, PLASMA_RELATIVE_DATA_INSTALL_DIR "/containmentactions", QStandardPaths::LocateDirectory)); + pkg.setDefaultPackageRoot(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral(PLASMA_RELATIVE_DATA_INSTALL_DIR "/containmentactions"), QStandardPaths::LocateDirectory)); m_containmentActionConfigModel->appendCategory(info.icon(), info.name(), pkg.filePath("ui", QStringLiteral("config.qml")), info.pluginName()); } } return m_containmentActionConfigModel; } QAbstractItemModel *ContainmentConfigView::currentContainmentActionsModel() { if (!m_currentContainmentActionsModel) { m_currentContainmentActionsModel = new CurrentContainmentActionsModel(m_containment, this); } return m_currentContainmentActionsModel; } QString ContainmentConfigView::containmentPlugin() const { return m_containment->pluginMetaData().pluginId(); } void ContainmentConfigView::setContainmentPlugin(const QString &plugin) { if (plugin.isEmpty() || containmentPlugin() == plugin) { return; } m_containment = static_cast(m_containment->corona())->setContainmentTypeForScreen(m_containment->screen(), plugin); emit containmentPluginChanged(); } PlasmaQuick::ConfigModel *ContainmentConfigView::wallpaperConfigModel() { if (!m_wallpaperConfigModel) { m_wallpaperConfigModel = new PlasmaQuick::ConfigModel(this); - for (const KPluginMetaData &m : KPackage::PackageLoader::self()->listPackages("Plasma/Wallpaper")) { - KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage("Plasma/Wallpaper", m.pluginId()); + for (const KPluginMetaData &m : KPackage::PackageLoader::self()->listPackages(QStringLiteral("Plasma/Wallpaper"))) { + KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Wallpaper"), m.pluginId()); if (!pkg.isValid()) { continue; } m_wallpaperConfigModel->appendCategory(pkg.metadata().iconName(), pkg.metadata().name(), pkg.fileUrl("ui", QStringLiteral("config.qml")).toString(), m.pluginId()); } } return m_wallpaperConfigModel; } PlasmaQuick::ConfigModel *ContainmentConfigView::containmentPluginsConfigModel() { if (!m_containmentPluginsConfigModel) { m_containmentPluginsConfigModel = new PlasmaQuick::ConfigModel(this); KPluginInfo::List actions = Plasma::PluginLoader::self()->listContainmentsOfType(QStringLiteral("Desktop")); foreach (const KPluginInfo &info, actions) { m_containmentPluginsConfigModel->appendCategory(info.icon(), info.name(), QString(), info.pluginName()); } } return m_containmentPluginsConfigModel; } KDeclarative::ConfigPropertyMap *ContainmentConfigView::wallpaperConfiguration() const { return m_currentWallpaperConfig; } QString ContainmentConfigView::currentWallpaper() const { return m_currentWallpaper; } void ContainmentConfigView::setCurrentWallpaper(const QString &wallpaper) { if (m_currentWallpaper == wallpaper) { return; } delete m_ownWallpaperConfig; m_ownWallpaperConfig = nullptr; if (m_containment->wallpaper() == wallpaper) { syncWallpaperObjects(); } else { //we have to construct an independent ConfigPropertyMap when we want to configure wallpapers that are not the current one KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Generic")); - pkg.setDefaultPackageRoot(PLASMA_RELATIVE_DATA_INSTALL_DIR "/wallpapers"); + pkg.setDefaultPackageRoot(QStringLiteral(PLASMA_RELATIVE_DATA_INSTALL_DIR "/wallpapers")); pkg.setPath(wallpaper); QFile file(pkg.filePath("config", QStringLiteral("main.xml"))); KConfigGroup cfg = m_containment->config(); cfg = KConfigGroup(&cfg, "Wallpaper"); cfg = KConfigGroup(&cfg, wallpaper); m_currentWallpaperConfig = m_ownWallpaperConfig = new KDeclarative::ConfigPropertyMap(new KConfigLoader(cfg, &file), this); } m_currentWallpaper = wallpaper; emit currentWallpaperChanged(); emit wallpaperConfigurationChanged(); } void ContainmentConfigView::applyWallpaper() { m_containment->setWallpaper(m_currentWallpaper); syncWallpaperObjects(); if (m_currentWallpaperConfig && m_ownWallpaperConfig) { for (const auto &key : m_ownWallpaperConfig->keys()) { auto value = m_ownWallpaperConfig->value(key); m_currentWallpaperConfig->insert(key, value); m_currentWallpaperConfig->valueChanged(key, value); } } delete m_ownWallpaperConfig; m_ownWallpaperConfig = nullptr; emit wallpaperConfigurationChanged(); } void ContainmentConfigView::syncWallpaperObjects() { QObject *wallpaperGraphicsObject = m_containment->property("wallpaperGraphicsObject").value(); if (!wallpaperGraphicsObject) { return; } rootContext()->setContextProperty(QStringLiteral("wallpaper"), wallpaperGraphicsObject); //FIXME: why m_wallpaperGraphicsObject->property("configuration").value() doesn't work? m_currentWallpaperConfig = static_cast(wallpaperGraphicsObject->property("configuration").value()); } #include "moc_containmentconfigview.cpp" diff --git a/shell/currentcontainmentactionsmodel.cpp b/shell/currentcontainmentactionsmodel.cpp index 4e9cb9cc2..4d509b9a7 100644 --- a/shell/currentcontainmentactionsmodel.cpp +++ b/shell/currentcontainmentactionsmodel.cpp @@ -1,289 +1,289 @@ /* * Copyright 2013 Marco Martin * * 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. */ #include "currentcontainmentactionsmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include CurrentContainmentActionsModel::CurrentContainmentActionsModel(Plasma::Containment *cotainment, QObject *parent) : QStandardItemModel(parent), m_containment(cotainment), m_tempConfigParent(QString(), KConfig::SimpleConfig) { m_baseCfg = KConfigGroup(m_containment->corona()->config(), "ActionPlugins"); m_baseCfg = KConfigGroup(&m_baseCfg, QString::number(m_containment->containmentType())); QHash actions = cotainment->containmentActions(); QHashIterator i(actions); while (i.hasNext()) { i.next(); QStandardItem *item = new QStandardItem(); item->setData(i.key(), ActionRole); item->setData(i.value()->pluginInfo().pluginName(), PluginNameRole); m_plugins[i.key()] = Plasma::PluginLoader::self()->loadContainmentActions(m_containment, i.value()->pluginInfo().pluginName()); m_plugins[i.key()]->setContainment(m_containment); KConfigGroup cfg(&m_baseCfg, i.key()); m_plugins[i.key()]->restore(cfg); item->setData(m_plugins[i.key()]->pluginInfo().property(QStringLiteral("X-Plasma-HasConfigurationInterface")).toBool(), HasConfigurationInterfaceRole); appendRow(item); } } CurrentContainmentActionsModel::~CurrentContainmentActionsModel() { } QHash CurrentContainmentActionsModel::roleNames() const { return { { ActionRole, "action" }, { PluginNameRole, "pluginName" }, { HasConfigurationInterfaceRole, "hasConfigurationInterface" } }; } QString CurrentContainmentActionsModel::mouseEventString(int mouseButton, int modifiers) { QMouseEvent *mouse = new QMouseEvent(QEvent::MouseButtonRelease, QPoint(), (Qt::MouseButton)mouseButton, (Qt::MouseButton)mouseButton, (Qt::KeyboardModifiers) modifiers); const QString string = Plasma::ContainmentActions::eventToString(mouse); delete mouse; return string; } QString CurrentContainmentActionsModel::wheelEventString(const QPointF &delta, int mouseButtons, int modifiers) { QWheelEvent *wheel = new QWheelEvent(QPointF(), QPointF(), delta.toPoint(), QPoint(), 0, qAbs(delta.x()) > qAbs(delta.y()) ? Qt::Horizontal : Qt::Vertical, (Qt::MouseButtons)mouseButtons, (Qt::KeyboardModifiers) modifiers); const QString string = Plasma::ContainmentActions::eventToString(wheel); delete wheel; return string; } bool CurrentContainmentActionsModel::isTriggerUsed(const QString &trigger) { return m_plugins.contains(trigger); } bool CurrentContainmentActionsModel::append(const QString &action, const QString &plugin) { if (m_plugins.contains(action)) { return false; } QStandardItem *item = new QStandardItem(); item->setData(action, ActionRole); item->setData(plugin, PluginNameRole); Plasma::ContainmentActions *actions = Plasma::PluginLoader::self()->loadContainmentActions(m_containment, plugin); if (!actions) { return false; } m_plugins[action] = actions; m_plugins[action]->setContainment(m_containment); //empty config: the new one will ne in default state KConfigGroup tempConfig(&m_tempConfigParent, "test"); m_plugins[action]->restore(tempConfig); item->setData(m_plugins[action]->pluginInfo().property(QStringLiteral("X-Plasma-HasConfigurationInterface")).toBool(), HasConfigurationInterfaceRole); m_removedTriggers.removeAll(action); appendRow(item); emit configurationChanged(); return true; } void CurrentContainmentActionsModel::update(int row, const QString &action, const QString &plugin) { const QString oldPlugin = itemData(index(row, 0)).value(PluginNameRole).toString(); const QString oldTrigger = itemData(index(row, 0)).value(ActionRole).toString(); if (oldTrigger == action && oldPlugin == plugin) { return; } QModelIndex idx = index(row, 0); if (idx.isValid()) { setData(idx, action, ActionRole); setData(idx, plugin, PluginNameRole); delete m_plugins[oldTrigger]; m_plugins.remove(oldTrigger); if (oldPlugin != plugin) { m_removedTriggers << oldTrigger; } if (!m_plugins.contains(action) || oldPlugin != plugin) { delete m_plugins[action]; m_plugins[action] = Plasma::PluginLoader::self()->loadContainmentActions(m_containment, plugin); m_plugins[action]->setContainment(m_containment); //empty config: the new one will ne in default state KConfigGroup tempConfig(&m_tempConfigParent, "test"); m_plugins[action]->restore(tempConfig); setData(idx, m_plugins[action]->pluginInfo().property(QStringLiteral("X-Plasma-HasConfigurationInterface")).toBool(), HasConfigurationInterfaceRole); } emit configurationChanged(); } } void CurrentContainmentActionsModel::remove(int row) { const QString action = itemData(index(row, 0)).value(ActionRole).toString(); removeRows(row, 1); if (m_plugins.contains(action)) { delete m_plugins[action]; m_plugins.remove(action); m_removedTriggers << action; emit configurationChanged(); } } void CurrentContainmentActionsModel::showConfiguration(int row, QQuickItem *ctx) { const QString action = itemData(index(row, 0)).value(ActionRole).toString(); if (!m_plugins.contains(action)) { return; } QDialog *configDlg = new QDialog(); configDlg->setAttribute(Qt::WA_DeleteOnClose); QLayout *lay = new QVBoxLayout(configDlg); configDlg->setLayout(lay); if (ctx && ctx->window()) { configDlg->setWindowModality(Qt::WindowModal); configDlg->winId(); // so it creates the windowHandle(); configDlg->windowHandle()->setTransientParent(ctx->window()); } Plasma::ContainmentActions *pluginInstance = m_plugins[action]; //put the config in the dialog QWidget *w = pluginInstance->createConfigurationInterface(configDlg); QString title; if (w) { lay->addWidget(w); title = w->windowTitle(); } configDlg->setWindowTitle(title.isEmpty() ? i18n("Configure Mouse Actions Plugin") :title); //put buttons below QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, configDlg); lay->addWidget(buttons); connect(buttons, &QDialogButtonBox::accepted, configDlg, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, configDlg, &QDialog::reject); QObject::connect(configDlg, &QDialog::accepted, pluginInstance, [configDlg, pluginInstance] () { pluginInstance->configurationAccepted(); }); connect(configDlg, &QDialog::accepted, this, &CurrentContainmentActionsModel::configurationChanged); connect(pluginInstance, &QObject::destroyed, configDlg, &QDialog::reject); configDlg->show(); } void CurrentContainmentActionsModel::showAbout(int row, QQuickItem *ctx) { const QString action = itemData(index(row, 0)).value(ActionRole).toString(); if (!m_plugins.contains(action)) { return; } KPluginInfo info = m_plugins[action]->pluginInfo(); - KAboutData aboutData(info.name().toUtf8(), + KAboutData aboutData(info.name(), ki18n(info.name().toUtf8()).toString(), - info.version().toUtf8(), + info.version(), ki18n(info.comment().toUtf8()).toString(), KAboutLicense::byKeyword(info.license()).key(), - QByteArray(), - QByteArray(), info.website().toLatin1(), - info.email().toLatin1()); + QString(), + QString(), info.website(), + info.email()); - aboutData.addAuthor(ki18n(info.author().toUtf8()).toString(), QByteArray(), info.email().toLatin1()); + aboutData.addAuthor(ki18n(info.author().toUtf8()).toString(), QString(), info.email()); KAboutApplicationDialog *aboutDialog = new KAboutApplicationDialog(aboutData, qobject_cast(parent())); aboutDialog->setWindowIcon(QIcon::fromTheme(info.icon())); aboutDialog->setAttribute(Qt::WA_DeleteOnClose); if (ctx && ctx->window()) { aboutDialog->setWindowModality(Qt::WindowModal); aboutDialog->winId(); // so it creates the windowHandle(); aboutDialog->windowHandle()->setTransientParent(ctx->window()); } aboutDialog->show(); } void CurrentContainmentActionsModel::save() { foreach (const QString &removedTrigger, m_removedTriggers) { m_containment->setContainmentActions(removedTrigger, QString()); } m_removedTriggers.clear(); QHashIterator i(m_plugins); while (i.hasNext()) { i.next(); KConfigGroup cfg(&m_baseCfg, i.key()); i.value()->save(cfg); m_containment->setContainmentActions(i.key(), i.value()->pluginInfo().pluginName()); } } #include "moc_currentcontainmentactionsmodel.cpp" diff --git a/shell/main.cpp b/shell/main.cpp index 6e2413600..e4945a244 100644 --- a/shell/main.cpp +++ b/shell/main.cpp @@ -1,213 +1,213 @@ /* * Copyright 2012 Marco Martin * Copyright 2013 Sebastian Kügler * Copyright 2015 David Edmundson * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "shellcorona.h" #include "standaloneappcorona.h" #include "shellmanager.h" #include "coronatesthelper.h" #include "softwarerendernotifier.h" #include int main(int argc, char *argv[]) { //Plasma scales itself to font DPI //on X, where we don't have compositor scaling, this generally works fine. //also there are bugs on older Qt, especially when it comes to fractional scaling //there's advantages to disabling, and (other than small context menu icons) few advantages in enabling //On wayland, it's different. Everything is simpler as all co-ordinates are in the same co-ordinate system //we don't have fractional scaling on the client so don't hit most the remaining bugs and //even if we don't use Qt scaling the compositor will try to scale us anyway so we have no choice if (!qEnvironmentVariableIsSet("PLASMA_USE_QT_SCALING")) { qunsetenv("QT_DEVICE_PIXEL_RATIO"); QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling); } else { QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); } QQuickWindow::setDefaultAlphaBuffer(true); const bool qpaVariable = qEnvironmentVariableIsSet("QT_QPA_PLATFORM"); KWorkSpace::detectPlatform(argc, argv); QApplication app(argc, argv); if (!qpaVariable) { // don't leak the env variable to processes we start qunsetenv("QT_QPA_PLATFORM"); } KLocalizedString::setApplicationDomain("plasmashell"); // The executable's path is added to the library/plugin paths. // This does not make much sense for plasmashell. app.removeLibraryPath(QCoreApplication::applicationDirPath()); KQuickAddons::QtQuickSettings::init(); KAboutData aboutData(QStringLiteral("plasmashell"), i18n("Plasma"), QStringLiteral(PROJECT_VERSION), i18n("Plasma Shell"), KAboutLicense::GPL); KAboutData::setApplicationData(aboutData); app.setQuitOnLastWindowClosed(false); { QCommandLineParser cliOptions; QCommandLineOption dbgOption(QStringList() << QStringLiteral("d") << QStringLiteral("qmljsdebugger"), i18n("Enable QML Javascript debugger")); QCommandLineOption noRespawnOption(QStringList() << QStringLiteral("n") << QStringLiteral("no-respawn"), i18n("Do not restart plasma-shell automatically after a crash")); QCommandLineOption shellPluginOption(QStringList() << QStringLiteral("p") << QStringLiteral("shell-plugin"), i18n("Force loading the given shell plugin"), QStringLiteral("plugin")); QCommandLineOption standaloneOption(QStringList() << QStringLiteral("a") << QStringLiteral("standalone"), i18n("Load plasmashell as a standalone application, needs the shell-plugin option to be specified")); QCommandLineOption replaceOption({QStringLiteral("replace")}, i18n("Replace an existing instance")); QCommandLineOption testOption(QStringList() << QStringLiteral("test"), i18n("Enables test mode and specifies the layout javascript file to set up the testing environment"), i18n("file"), QStringLiteral("layout.js")); cliOptions.addOption(dbgOption); cliOptions.addOption(noRespawnOption); cliOptions.addOption(shellPluginOption); cliOptions.addOption(standaloneOption); cliOptions.addOption(testOption); cliOptions.addOption(replaceOption); aboutData.setupCommandLine(&cliOptions); cliOptions.process(app); aboutData.processCommandLine(&cliOptions); QGuiApplication::setFallbackSessionManagementEnabled(false); auto disableSessionManagement = [](QSessionManager &sm) { sm.setRestartHint(QSessionManager::RestartNever); }; QObject::connect(&app, &QGuiApplication::commitDataRequest, disableSessionManagement); QObject::connect(&app, &QGuiApplication::saveStateRequest, disableSessionManagement); ShellManager::s_fixedShell = cliOptions.value(shellPluginOption); if (!cliOptions.isSet(noRespawnOption) && !cliOptions.isSet(testOption)) { KCrash::setFlags(KCrash::AutoRestart); } if (cliOptions.isSet(testOption)) { const QUrl layoutUrl = QUrl::fromUserInput(cliOptions.value(testOption), {}, QUrl::AssumeLocalFile); if (!layoutUrl.isLocalFile()) { qWarning() << "ensure the layout file is local" << layoutUrl; cliOptions.showHelp(1); } QStandardPaths::setTestModeEnabled(true); QDir(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)).removeRecursively(); ShellManager::s_testModeLayout = layoutUrl.toLocalFile(); qApp->setProperty("org.kde.KActivities.core.disableAutostart", true); QObject::connect(ShellManager::instance(), &ShellManager::shellChanged, ShellManager::instance(), [layoutUrl]() { new CoronaTestHelper(ShellManager::instance()->corona()); } ); } if (cliOptions.isSet(standaloneOption)) { if (cliOptions.isSet(shellPluginOption)) { ShellManager::s_standaloneOption = true; - app.setApplicationName("plasmashell_"+cliOptions.value(shellPluginOption)); + app.setApplicationName(QStringLiteral("plasmashell_") + cliOptions.value(shellPluginOption)); app.setQuitOnLastWindowClosed(true); KDBusService service(KDBusService::Unique); //This will not leak, because corona deletes itself on window close new StandaloneAppCorona(cliOptions.value(shellPluginOption)); return app.exec(); } else { cliOptions.showHelp(1); } } if (cliOptions.isSet(replaceOption)) { auto message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.plasmashell"), QStringLiteral("/MainApplication"), QStringLiteral("org.qtproject.Qt.QCoreApplication"), QStringLiteral("quit")); QDBusConnection::sessionBus().call(message); //deliberately block until it's done, so we register the name after the app quits } } KDBusService service(KDBusService::Unique); QObject::connect(ShellManager::instance(), &ShellManager::glInitializationFailed, &app, [&app]() { //scene graphs errors come from a thread //even though we process them in the main thread, app.exit could still process these events static bool s_multipleInvokations = false; if (s_multipleInvokations) { return; } s_multipleInvokations = true; qCritical("Open GL context could not be created"); auto configGroup = KSharedConfig::openConfig()->group("QtQuickRendererSettings"); if (configGroup.readEntry("SceneGraphBackend") != QLatin1String("software")) { configGroup.writeEntry("SceneGraphBackend", "software", KConfigBase::Global | KConfigBase::Persistent); configGroup.sync(); - QProcess::startDetached("plasmashell", app.arguments()); + QProcess::startDetached(QStringLiteral("plasmashell"), app.arguments()); } else { QCoreApplication::setAttribute(Qt::AA_ForceRasterWidgets); QMessageBox::critical(nullptr, i18n("Plasma Failed To Start"), i18n("Plasma is unable to start as it could not correctly use OpenGL 2 or software fallback\nPlease check that your graphic drivers are set up correctly.")); } app.exit(-1); }); SoftwareRendererNotifier::notifyIfRelevant(); QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, ShellManager::instance(), &QObject::deleteLater); return app.exec(); } diff --git a/shell/panelview.cpp b/shell/panelview.cpp index dbdf356f0..184962052 100644 --- a/shell/panelview.cpp +++ b/shell/panelview.cpp @@ -1,1263 +1,1263 @@ /* * 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 #if HAVE_X11 #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_background(nullptr), 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())); 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, "Vertical" + QString::number(screen->size().height())); + return KConfigGroup(&views, QStringLiteral("Vertical") + QString::number(screen->size().height())); //treat everything else as horizontal } else { - return KConfigGroup(&views, "Horizontal" + QString::number(screen->size().width())); + 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(); KWindowSystem::setState(m_panelConfigView.data()->winId(), NET::SkipTaskbar | NET::SkipPager); } 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) { updateMask(); 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); } void PanelView::moveEvent(QMoveEvent *ev) { updateMask(); updateEnabledBorders(); m_strutsTimer.start(STRUTSTIMERDELAY); PlasmaQuick::ContainmentView::moveEvent(ev); } void PanelView::integrateScreen() { connect(m_screenToFollow.data(), &QScreen::geometryChanged, this, &PanelView::restore); themeChanged(); KWindowSystem::setOnAllDesktops(winId(), true); KWindowSystem::setType(winId(), NET::Dock); 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() { if (KWindowSystem::compositingActive()) { setMask(QRegion()); } else { if (!m_background) { m_background = new Plasma::FrameSvg(this); m_background->setImagePath(QStringLiteral("widgets/panel-background")); } m_background->setEnabledBorders(enabledBorders()); m_background->resizeFrame(size()); setMask(m_background->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? foreach (int id, m_corona->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(), true); KWindowEffects::enableBackgroundContrast(winId(), m_theme.backgroundContrastEnabled(), m_theme.backgroundContrast(), m_theme.backgroundIntensity(), m_theme.backgroundSaturation()); } updateMask(); } 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("Panel " + QString::number(containment()->id()) + "$"))) { + 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::statusChanged(Plasma::Types::ItemStatus status) { if (status == Plasma::Types::NeedsAttentionStatus) { showTemporarily(); } else if (status == Plasma::Types::AcceptingInputStatus) { KWindowSystem::forceActiveWindow(winId()); } else { restoreAutoHide(); } } 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"