add_library(dolphinstatic STATIC)
target_sources(dolphinstatic PRIVATE
+ admin/bar.cpp
+ admin/workerintegration.cpp
animatedheightwidget.cpp
disabledactionnotifier.cpp
dolphinbookmarkhandler.cpp
global.cpp
dolphin.qrc
+ admin/bar.h
+ admin/workerintegration.h
animatedheightwidget.h
dolphinbookmarkhandler.h
dolphindockwidget.h
--- /dev/null
+/*
+ This file is part of the KDE project
+ SPDX-FileCopyrightText: 2024 Felix Ernst <felixernst@kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "bar.h"
+
+#include "workerintegration.h"
+
+#include <KColorScheme>
+#include <KContextualHelpButton>
+#include <KLocalizedString>
+
+#include <QEvent>
+#include <QGuiApplication>
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QPushButton>
+#include <QStyle>
+#include <QToolButton>
+#include <QWidgetAction>
+
+using namespace Admin;
+
+Bar::Bar(QWidget *parent)
+ : AnimatedHeightWidget{parent}
+{
+ setAutoFillBackground(true);
+ updateColors();
+
+ QWidget *contenntsContainer = prepareContentsContainer();
+
+ m_fullLabelString = i18nc("@info label above the view explaining the state", "Acting as an Administrator – Be careful!");
+ m_shortLabelString = i18nc("@info label above the view explaining the state, keep short", "Acting as Admin");
+ m_label = new QLabel(contenntsContainer);
+ m_label->setMinimumWidth(0);
+ m_label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard | Qt::LinksAccessibleByKeyboard); // for keyboard accessibility
+
+ m_warningButton = new KContextualHelpButton(warningMessage(), nullptr, contenntsContainer);
+ m_warningButton->setIcon(QIcon::fromTheme(QStringLiteral("emblem-warning")));
+
+ m_closeButton = new QPushButton(QIcon::fromTheme(QStringLiteral("window-close-symbolic")), "", contenntsContainer);
+ m_closeButton->setToolTip(i18nc("@action:button", "Stop Acting as an Administrator"));
+ m_closeButton->setFlat(true);
+ connect(m_closeButton, &QAbstractButton::clicked, this, &Bar::activated); // Make sure the view connected to this bar is active before exiting admin mode.
+ connect(m_closeButton, &QAbstractButton::clicked, this, &WorkerIntegration::exitAdminMode);
+
+ QHBoxLayout *layout = new QHBoxLayout(contenntsContainer);
+ auto contentsMargins = layout->contentsMargins();
+ m_preferredHeight = contentsMargins.top() + m_label->sizeHint().height() + contentsMargins.bottom();
+ m_warningButton->setFixedHeight(m_preferredHeight);
+ m_closeButton->setFixedHeight(m_preferredHeight);
+ layout->setContentsMargins(0, 0, 0, 0);
+
+ layout->addStretch();
+ layout->addWidget(m_label);
+ layout->addWidget(m_warningButton);
+ layout->addStretch();
+ layout->addWidget(m_closeButton);
+}
+
+bool Bar::event(QEvent *event)
+{
+ if (event->type() == QEvent::PaletteChange) {
+ updateColors();
+ }
+ return AnimatedHeightWidget::event(event);
+}
+
+void Bar::resizeEvent(QResizeEvent *resizeEvent)
+{
+ updateLabelString();
+ return QWidget::resizeEvent(resizeEvent);
+}
+
+void Bar::updateColors()
+{
+ QPalette palette = parentWidget()->palette();
+ KColorScheme colorScheme{QPalette::Normal, KColorScheme::ColorSet::Window};
+ colorScheme.adjustBackground(palette, KColorScheme::NegativeBackground, QPalette::Window, KColorScheme::ColorSet::Window);
+ colorScheme.adjustForeground(palette, KColorScheme::NegativeText, QPalette::WindowText, KColorScheme::ColorSet::Window);
+ setPalette(palette);
+}
+
+void Bar::updateLabelString()
+{
+ QFontMetrics fontMetrics = m_label->fontMetrics();
+ if (fontMetrics.horizontalAdvance(m_fullLabelString) + m_warningButton->sizeHint().width() + m_closeButton->sizeHint().width()
+ + style()->pixelMetric(QStyle::PM_LayoutLeftMargin) * 2 + style()->pixelMetric(QStyle::PM_LayoutRightMargin) * 2
+ < width()) {
+ m_label->setText(m_fullLabelString);
+ } else {
+ m_label->setText(m_shortLabelString);
+ }
+}
+
+#include "moc_bar.cpp"
--- /dev/null
+/*
+ This file is part of the KDE project
+ SPDX-FileCopyrightText: 2024 Felix Ernst <felixernst@kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ADMINBAR_H
+#define ADMINBAR_H
+
+#include "animatedheightwidget.h"
+
+class QLabel;
+class QPushButton;
+class QResizeEvent;
+class QToolButton;
+
+namespace Admin
+{
+
+/**
+ * @brief A bar appearing above the view while the user is acting with administrative privileges.
+ *
+ * It contains a warning and allows revoking this administrative mode by closing this bar.
+ */
+class Bar : public AnimatedHeightWidget
+{
+ Q_OBJECT
+
+public:
+ explicit Bar(QWidget *parent);
+
+ /** Used to recolor this bar when this application's color scheme changes. */
+ bool event(QEvent *event) override;
+
+Q_SIGNALS:
+ void activated();
+
+protected:
+ /** Calls updateLabelString() */
+ void resizeEvent(QResizeEvent *resizeEvent) override;
+
+private:
+ /** Recolors this bar based on the current color scheme. */
+ void updateColors();
+
+ /** Decides whether the m_fullLabelString or m_shortLabelString should be used based on available width. */
+ void updateLabelString();
+
+ /** @see AnimatedHeightWidget::preferredHeight() */
+ inline int preferredHeight() const override
+ {
+ return m_preferredHeight;
+ };
+
+private:
+ /** The text on this bar */
+ QLabel *m_label = nullptr;
+ /** Shows a warning about the dangers of acting with administrative privileges. */
+ QToolButton *m_warningButton = nullptr;
+ /** Closes this bar and exits the administrative mode. */
+ QPushButton *m_closeButton = nullptr;
+
+ /** @see updateLabelString() */
+ QString m_fullLabelString;
+ /** @see updateLabelString() */
+ QString m_shortLabelString;
+
+ /** @see preferredHeight() */
+ int m_preferredHeight;
+};
+
+}
+
+#endif // ADMINBAR_H
--- /dev/null
+/*
+ This file is part of the KDE project
+ SPDX-FileCopyrightText: 2022 Felix Ernst <felixernst@kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "workerintegration.h"
+
+#include "dolphinmainwindow.h"
+#include "dolphinviewcontainer.h"
+
+#include <KActionCollection>
+#include <KLocalizedString>
+#include <KMessageBox>
+#include <KMessageDialog>
+#include <KProtocolInfo>
+
+#include <QAction>
+
+using namespace Admin;
+
+QString Admin::warningMessage()
+{
+ return xi18nc(
+ "@info",
+ "<para>You are about to use administrator privileges. While acting as an administrator you can change or replace any file or folder on this system. "
+ "This includes items which are critical for this system to function.</para><para>You are able to <emphasis>delete every users' data</emphasis> on this "
+ "computer and to <emphasis>break this installation beyond repair.</emphasis> Adding just one letter in a folder or file name or its contents can "
+ "render a system <emphasis>unbootable.</emphasis></para><para>There is probably not going to be another warning even if you are about to break this "
+ "system.</para><para>You might want to <emphasis>backup files and folders</emphasis> before proceeding.</para>");
+}
+
+namespace
+{
+/** The only WorkerIntegration object that is ever constructed. It is only ever accessed directly from within this file. */
+WorkerIntegration *instance = nullptr;
+}
+
+WorkerIntegration::WorkerIntegration(DolphinMainWindow *parent, QAction *actAsAdminAction)
+ : QObject{parent}
+ , m_actAsAdminAction{actAsAdminAction}
+{
+ Q_CHECK_PTR(parent);
+ Q_CHECK_PTR(actAsAdminAction);
+
+ connect(parent, &DolphinMainWindow::urlChanged, this, &WorkerIntegration::updateActAsAdminAction);
+
+ connect(actAsAdminAction, &QAction::triggered, this, &WorkerIntegration::toggleActAsAdmin);
+}
+
+void WorkerIntegration::createActAsAdminAction(KActionCollection *actionCollection, DolphinMainWindow *dolphinMainWindow)
+{
+ Q_ASSERT(!instance);
+ if (KProtocolInfo::isKnownProtocol(QStringLiteral("admin"))) {
+ QAction *actAsAdminAction = actionCollection->addAction(QStringLiteral("act_as_admin"));
+ actAsAdminAction->setText(i18nc("@action:inmenu", "Act as Administrator"));
+ actAsAdminAction->setIcon(QIcon::fromTheme(QStringLiteral("system-switch-user")));
+ actAsAdminAction->setCheckable(true);
+ actionCollection->setDefaultShortcut(actAsAdminAction, Qt::CTRL | Qt::SHIFT | Qt::ALT | Qt::Key_A);
+
+ instance = new WorkerIntegration(dolphinMainWindow, actAsAdminAction);
+ }
+}
+
+void WorkerIntegration::exitAdminMode()
+{
+ if (instance->m_actAsAdminAction->isChecked()) {
+ instance->m_actAsAdminAction->trigger();
+ }
+}
+
+void WorkerIntegration::toggleActAsAdmin()
+{
+ auto dolphinMainWindow = static_cast<DolphinMainWindow *>(parent());
+ QUrl url = dolphinMainWindow->activeViewContainer()->urlNavigator()->locationUrl();
+ if (url.scheme() == QStringLiteral("file")) {
+ bool risksAccepted = !KMessageBox::shouldBeShownContinue(warningDontShowAgainName);
+
+ if (!risksAccepted) {
+ KMessageDialog warningDialog{KMessageDialog::QuestionTwoActions, warningMessage(), dolphinMainWindow};
+ warningDialog.setCaption(i18nc("@title:window", "Risks of Acting as an Administrator"));
+ warningDialog.setIcon(QIcon::fromTheme(QStringLiteral("security-low")));
+ warningDialog.setButtons(KGuiItem{i18nc("@action:button", "I Understand and Accept These Risks"), QStringLiteral("data-warning")},
+ KStandardGuiItem::cancel());
+ warningDialog.setDontAskAgainText(i18nc("@option:check", "Do not warn me about these risks again"));
+
+ risksAccepted = warningDialog.exec() != 4 /* Cancel */;
+ if (warningDialog.isDontAskAgainChecked()) {
+ KMessageBox::saveDontShowAgainContinue(warningDontShowAgainName);
+ }
+
+ if (!risksAccepted) {
+ updateActAsAdminAction(); // Uncheck the action
+ return;
+ }
+ }
+
+ url.setScheme(QStringLiteral("admin"));
+ } else if (url.scheme() == QStringLiteral("admin")) {
+ url.setScheme(QStringLiteral("file"));
+ }
+ dolphinMainWindow->changeUrl(url);
+}
+
+void WorkerIntegration::updateActAsAdminAction()
+{
+ if (instance) {
+ const QString currentUrlScheme = static_cast<DolphinMainWindow *>(instance->parent())->activeViewContainer()->url().scheme();
+ if (currentUrlScheme == QStringLiteral("file")) {
+ instance->m_actAsAdminAction->setEnabled(true);
+ instance->m_actAsAdminAction->setChecked(false);
+ } else if (currentUrlScheme == QStringLiteral("admin")) {
+ instance->m_actAsAdminAction->setEnabled(true);
+ instance->m_actAsAdminAction->setChecked(true);
+ } else {
+ instance->m_actAsAdminAction->setEnabled(false);
+ }
+ }
+}
--- /dev/null
+/*
+ This file is part of the KDE project
+ SPDX-FileCopyrightText: 2022 Felix Ernst <felixernst@kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ADMINWORKERINTEGRATION_H
+#define ADMINWORKERINTEGRATION_H
+
+#include <QObject>
+
+class DolphinMainWindow;
+class KActionCollection;
+class QAction;
+class QUrl;
+
+/**
+ * @brief This namespace contains everything that is necessary to nicely integrate "KIO Admin Worker" into Dolphin.
+ *
+ * @see https://commits.kde.org/kio-admin
+ */
+namespace Admin
+{
+/**
+ * Used with the KMessageBox API so users can disable the warning.
+ * @see KMessageBox::saveDontShowAgainContinue()
+ * @see KMessageBox::enableMessage()
+ */
+constexpr QLatin1String warningDontShowAgainName{"warnAboutRisksBeforeActingAsAdmin"};
+
+/** @returns an elaborate warning about the dangers of acting with administrative privileges. */
+QString warningMessage();
+
+/**
+ * @brief A class encapsulating the "Act as Admin"-toggle action.
+ *
+ * @see https://commits.kde.org/kio-admin
+ */
+class WorkerIntegration : public QObject
+{
+ Q_OBJECT
+
+public:
+ /**
+ * Adds a toggle action to the \a actionCollection.
+ * The action switches between acting as a normal user or acting as an administrator.
+ */
+ static void createActAsAdminAction(KActionCollection *actionCollection, DolphinMainWindow *dolphinMainWindow);
+
+ /**
+ * Triggers the m_actAsAdminAction only if it is currently checked.
+ */
+ static void exitAdminMode();
+
+private:
+ WorkerIntegration(DolphinMainWindow *parent, QAction *actAsAdminAction);
+
+ /**
+ * Toggles between acting with admin rights or not.
+ * This enables editing more files than the normal user account would be allowed to but requires re-authorization.
+ */
+ void toggleActAsAdmin();
+
+ /** Updates the toggled/checked state of the action depending on the state of the currently active view. */
+ static void updateActAsAdminAction();
+
+private:
+ /** @see createActAsAdminAction() */
+ QAction *const m_actAsAdminAction = nullptr;
+};
+
+}
+
+#endif // ADMINWORKERINTEGRATION_H
#include "dolphinmainwindow.h"
+#include "admin/workerintegration.h"
#include "dolphin_generalsettings.h"
#include "dolphinbookmarkhandler.h"
#include "dolphincontextmenu.h"
// setup 'View' menu
// (note that most of it is set up in DolphinViewActionHandler)
+ Admin::WorkerIntegration::createActAsAdminAction(actionCollection(), this);
+
m_splitViewAction = actionCollection()->add<KActionMenu>(QStringLiteral("split_view"));
m_splitViewMenuAction = actionCollection()->addAction(QStringLiteral("split_view_menu"));
<?xml version="1.0"?>
<!DOCTYPE gui SYSTEM "kpartgui.dtd">
-<gui name="dolphin" version="39">
+<gui name="dolphin" version="40">
<MenuBar>
<Menu name="file">
<Action name="new_menu" />
<Action name="show_preview" />
<Action name="show_in_groups" />
<Action name="show_hidden_files" />
+ <Action name="act_as_admin" />
<Separator/>
<Action name="split_view_menu" />
<Action name="popout_split_view" />
#include "dolphinviewcontainer.h"
+#include "admin/bar.h"
#include "dolphin_compactmodesettings.h"
#include "dolphin_contentdisplaysettings.h"
#include "dolphin_detailsmodesettings.h"
// An overview of the widgets contained by this ViewContainer
struct LayoutStructure {
int searchBox = 0;
- int messageWidget = 1;
- int selectionModeTopBar = 2;
- int view = 3;
- int selectionModeBottomBar = 4;
- int filterBar = 5;
- int statusBar = 6;
+ int adminBar = 1;
+ int messageWidget = 2;
+ int selectionModeTopBar = 3;
+ int view = 4;
+ int selectionModeBottomBar = 5;
+ int filterBar = 6;
+ int statusBar = 7;
};
constexpr LayoutStructure positionFor;
, m_urlNavigatorConnected{nullptr}
, m_searchBox(nullptr)
, m_searchModeEnabled(false)
+ , m_adminBar{nullptr}
, m_messageWidget(nullptr)
, m_selectionModeTopBar{nullptr}
, m_view(nullptr)
connect(m_view, &DolphinView::hiddenFilesShownChanged, this, &DolphinViewContainer::slotHiddenFilesShownChanged);
connect(m_view, &DolphinView::sortHiddenLastChanged, this, &DolphinViewContainer::slotSortHiddenLastChanged);
connect(m_view, &DolphinView::currentDirectoryRemoved, this, &DolphinViewContainer::slotCurrentDirectoryRemoved);
+ connect(m_view, &DolphinView::urlChanged, this, &DolphinViewContainer::updateAdminBarVisibility);
// Initialize status bar
m_statusBar = new DolphinStatusBar(this);
m_topLayout->addWidget(m_statusBar, positionFor.statusBar, 0);
setSearchModeEnabled(isSearchUrl(url));
+ updateAdminBarVisibility(url);
// Update view as the ContentDisplaySettings change
// this happens here and not in DolphinView as DolphinviewContainer and DolphinView are not in the same build target ATM
}
}
+void DolphinViewContainer::updateAdminBarVisibility(const QUrl &url)
+{
+ if (url.scheme() == QStringLiteral("admin")) {
+ if (!m_adminBar) {
+ m_adminBar = new Admin::Bar(this);
+ m_topLayout->addWidget(m_adminBar, positionFor.adminBar, 0);
+ connect(m_adminBar, &Admin::Bar::activated, this, &DolphinViewContainer::activate);
+ }
+ m_adminBar->setVisible(true, WithAnimation);
+ } else if (m_adminBar) {
+ m_adminBar->setVisible(false, WithAnimation);
+ }
+}
+
void DolphinViewContainer::closeFilterBar()
{
m_filterBar->closeFilterBar();
#include <QPushButton>
#include <QWidget>
+namespace Admin
+{
+class Bar;
+}
class FilterBar;
class KMessageWidget;
class QAction;
*/
void showItemInfo(const KFileItem &item);
+ /**
+ * Sets the Admin::Bar visible or invisible based on whether \a url is an admin url.
+ */
+ void updateAdminBarVisibility(const QUrl &url);
+
void closeFilterBar();
/**
DolphinSearchBox *m_searchBox;
bool m_searchModeEnabled;
+ /// A bar shown at the top of the view to signify that the view is currently viewed and acted on with elevated privileges.
+ Admin::Bar *m_adminBar;
+
KMessageWidget *m_messageWidget;
/// A bar shown at the top of the view to signify that selection mode is currently active.
#include "confirmationssettingspage.h"
+#include "admin/workerintegration.h"
#include "dolphin_generalsettings.h"
#include "global.h"
#include <KLocalizedString>
+#include <KMessageBox>
+#include <KProtocolInfo>
#include <QCheckBox>
#include <QComboBox>
m_confirmOpenManyFolders = new QCheckBox(i18nc("@option:check Ask for confirmation in Dolphin when", "Opening many folders at once"), this);
m_confirmOpenManyTerminals = new QCheckBox(i18nc("@option:check Ask for confirmation in Dolphin when", "Opening many terminals at once"), this);
+ m_confirmRisksOfActingAsAdmin = new QCheckBox(i18nc("@option:check Ask for confirmation in Dolphin when", "Switching to act as an administrator"), this);
QLabel *executableScriptLabel = new QLabel(i18nc("@title:group", "When opening an executable file:"), this);
executableScriptLabel->setWordWrap(true);
topLayout->addRow(nullptr, m_confirmOpenManyFolders);
topLayout->addRow(nullptr, m_confirmOpenManyTerminals);
+ if (KProtocolInfo::isKnownProtocol(QStringLiteral("admin"))) {
+ topLayout->addRow(nullptr, m_confirmRisksOfActingAsAdmin);
+ } else {
+ m_confirmRisksOfActingAsAdmin->hide();
+ }
topLayout->addItem(new QSpacerItem(0, Dolphin::VERTICAL_SPACER_HEIGHT, QSizePolicy::Fixed, QSizePolicy::Fixed));
topLayout->addRow(executableScriptLabel, m_confirmScriptExecution);
connect(m_confirmClosingMultipleTabs, &QCheckBox::toggled, this, &ConfirmationsSettingsPage::changed);
connect(m_confirmOpenManyFolders, &QCheckBox::toggled, this, &ConfirmationsSettingsPage::changed);
connect(m_confirmOpenManyTerminals, &QCheckBox::toggled, this, &ConfirmationsSettingsPage::changed);
+ connect(m_confirmRisksOfActingAsAdmin, &QCheckBox::toggled, this, &ConfirmationsSettingsPage::changed);
#if HAVE_TERMINAL
connect(m_confirmClosingTerminalRunningProgram, &QCheckBox::toggled, this, &ConfirmationsSettingsPage::changed);
settings->setConfirmClosingMultipleTabs(m_confirmClosingMultipleTabs->isChecked());
settings->setConfirmOpenManyFolders(m_confirmOpenManyFolders->isChecked());
settings->setConfirmOpenManyTerminals(m_confirmOpenManyTerminals->isChecked());
+ if (m_confirmRisksOfActingAsAdmin->isChecked()) {
+ KMessageBox::enableMessage(Admin::warningDontShowAgainName);
+ } else {
+ KMessageBox::saveDontShowAgainContinue(Admin::warningDontShowAgainName);
+ }
#if HAVE_TERMINAL
settings->setConfirmClosingTerminalRunningProgram(m_confirmClosingTerminalRunningProgram->isChecked());
m_confirmEmptyTrash->setChecked(ConfirmEmptyTrash);
m_confirmDelete->setChecked(ConfirmDelete);
m_confirmScriptExecution->setCurrentIndex(ConfirmScriptExecution);
+ KMessageBox::enableMessage(Admin::warningDontShowAgainName);
}
void ConfirmationsSettingsPage::loadSettings()
// the UI has inversed meaning compared to the interpretation
m_confirmOpenManyFolders->setChecked(GeneralSettings::confirmOpenManyFolders());
m_confirmOpenManyTerminals->setChecked(GeneralSettings::confirmOpenManyTerminals());
+ m_confirmRisksOfActingAsAdmin->setChecked(KMessageBox::shouldBeShownContinue(Admin::warningDontShowAgainName));
#if HAVE_TERMINAL
m_confirmClosingTerminalRunningProgram->setChecked(GeneralSettings::confirmClosingTerminalRunningProgram());
QComboBox *m_confirmScriptExecution;
QCheckBox *m_confirmOpenManyFolders;
QCheckBox *m_confirmOpenManyTerminals;
+ QCheckBox *m_confirmRisksOfActingAsAdmin;
};
#endif