File indexing completed on 2024-04-28 05:36:53

0001 /*
0002  * SPDX-FileCopyrightText: 2018 Red Hat Inc
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  *
0006  * SPDX-FileCopyrightText: 2018 Jan Grulich <jgrulich@redhat.com>
0007  */
0008 
0009 #include "screenchooserdialog.h"
0010 #include "utils.h"
0011 #include "waylandintegration.h"
0012 
0013 #include "region-select/SelectionEditor.h"
0014 
0015 #include <KLocalizedString>
0016 #include <KWayland/Client/plasmawindowmanagement.h>
0017 #include <KWayland/Client/plasmawindowmodel.h>
0018 
0019 #include <QCoreApplication>
0020 #include <QScreen>
0021 #include <QSettings>
0022 #include <QSortFilterProxyModel>
0023 #include <QStandardPaths>
0024 #include <QTimer>
0025 #include <QWindow>
0026 
0027 class FilteredWindowModel : public QSortFilterProxyModel
0028 {
0029     Q_OBJECT
0030     Q_PROPERTY(bool hasSelection READ hasSelection NOTIFY hasSelectionChanged)
0031 public:
0032     FilteredWindowModel(QObject *parent)
0033         : QSortFilterProxyModel(parent)
0034     {
0035     }
0036 
0037     bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
0038     {
0039         if (source_parent.isValid())
0040             return false;
0041 
0042         const auto idx = sourceModel()->index(source_row, 0);
0043         using KWayland::Client::PlasmaWindowModel;
0044 
0045         return !idx.data(PlasmaWindowModel::SkipTaskbar).toBool() //
0046             && !idx.data(PlasmaWindowModel::SkipSwitcher).toBool() //
0047             && idx.data(PlasmaWindowModel::Pid) != QCoreApplication::applicationPid();
0048     }
0049 
0050     QMap<int, QVariant> itemData(const QModelIndex &index) const override
0051     {
0052         using KWayland::Client::PlasmaWindowModel;
0053         auto ret = QSortFilterProxyModel::itemData(index);
0054         for (int i = PlasmaWindowModel::AppId; i <= PlasmaWindowModel::Uuid; ++i) {
0055             ret[i] = index.data(i);
0056         }
0057         return ret;
0058     }
0059 
0060     bool setData(const QModelIndex &index, const QVariant &value, int role) override
0061     {
0062         if (!checkIndex(index, CheckIndexOption::IndexIsValid) || role != Qt::CheckStateRole) {
0063             return false;
0064         }
0065 
0066         using KWayland::Client::PlasmaWindowModel;
0067         const QString uuid = index.data(PlasmaWindowModel::Uuid).toString();
0068         if (value == Qt::Checked) {
0069             m_selected.insert(index);
0070         } else {
0071             m_selected.remove(index);
0072         }
0073         Q_EMIT dataChanged(index, index, {role});
0074         if (m_selected.count() <= 1) {
0075             Q_EMIT hasSelectionChanged();
0076         }
0077         return true;
0078     }
0079 
0080     QList<QMap<int, QVariant>> selectedWindows() const
0081     {
0082         QList<QMap<int, QVariant>> ret;
0083         ret.reserve(m_selected.size());
0084         for (const auto &index : m_selected) {
0085             if (index.isValid())
0086                 ret << itemData(index);
0087         }
0088         return ret;
0089     }
0090 
0091     QHash<int, QByteArray> roleNames() const override
0092     {
0093         QHash<int, QByteArray> ret = sourceModel()->roleNames();
0094         ret.insert(Qt::CheckStateRole, "checked");
0095         return ret;
0096     }
0097 
0098     QVariant data(const QModelIndex &index, int role) const override
0099     {
0100         if (!checkIndex(index, CheckIndexOption::IndexIsValid)) {
0101             return {};
0102         }
0103 
0104         switch (role) {
0105         case Qt::CheckStateRole:
0106             return m_selected.contains(index) ? Qt::Checked : Qt::Unchecked;
0107         default:
0108             return QSortFilterProxyModel::data(index, role);
0109         }
0110         return {};
0111     }
0112 
0113     bool hasSelection()
0114     {
0115         return !m_selected.isEmpty();
0116     }
0117 
0118     void clearSelection()
0119     {
0120         auto selected = m_selected;
0121         m_selected.clear();
0122 
0123         for (const auto &index : selected) {
0124             if (index.isValid())
0125                 Q_EMIT dataChanged(index, index, {Qt::CheckStateRole});
0126         }
0127         Q_EMIT hasSelectionChanged();
0128     }
0129 
0130 Q_SIGNALS:
0131     void hasSelectionChanged();
0132 
0133 private:
0134     QSet<QPersistentModelIndex> m_selected;
0135 };
0136 
0137 ScreenChooserDialog::ScreenChooserDialog(const QString &appName, bool multiple, ScreenCastPortal::SourceTypes types)
0138     : QuickDialog()
0139 {
0140     Q_ASSERT(types != 0);
0141 
0142     QVariantMap props = {
0143         {"title", i18n("Screen Sharing")},
0144         {"multiple", multiple},
0145     };
0146 
0147     int numberOfMonitors = 0;
0148     if (types & ScreenCastPortal::Monitor) {
0149         auto model =
0150             new OutputsModel(OutputsModel::Options(OutputsModel::WorkspaceIncluded | OutputsModel::VirtualIncluded | OutputsModel::RegionIncluded), this);
0151         props.insert("outputsModel", QVariant::fromValue<QObject *>(model));
0152         numberOfMonitors += model->rowCount(QModelIndex());
0153         connect(this, &ScreenChooserDialog::clearSelection, model, &OutputsModel::clearSelection);
0154     }
0155 
0156     int numberOfWindows = 0;
0157     if (types & ScreenCastPortal::Window) {
0158         auto model = new KWayland::Client::PlasmaWindowModel(WaylandIntegration::plasmaWindowManagement());
0159         auto windowsProxy = new FilteredWindowModel(this);
0160         windowsProxy->setSourceModel(model);
0161         props.insert("windowsModel", QVariant::fromValue<QObject *>(windowsProxy));
0162         connect(this, &ScreenChooserDialog::clearSelection, windowsProxy, &FilteredWindowModel::clearSelection);
0163         numberOfWindows += model->rowCount(QModelIndex());
0164     }
0165 
0166     const QString applicationName = Utils::applicationName(appName);
0167 
0168     QString mainText;
0169 
0170     // App asked for monitors and windows
0171     if (types & ScreenCastPortal::Monitor && types & ScreenCastPortal::Window) {
0172         if (appName.isEmpty()) {
0173             mainText = i18n("Choose what to share with the requesting application:");
0174         } else {
0175             mainText = i18n("Choose what to share with %1:", applicationName);
0176         }
0177     }
0178 
0179     // App only asked for monitors
0180     else if (types & ScreenCastPortal::Monitor) {
0181         if (numberOfMonitors == 1) {
0182             if (appName.isEmpty()) {
0183                 mainText = i18n("Share this screen with the requesting application?");
0184             } else {
0185                 mainText = i18n("Share this screen with %1?", applicationName);
0186             }
0187         } else {
0188             if (multiple) {
0189                 if (appName.isEmpty()) {
0190                     mainText = i18n("Choose screens to share with the requesting application:");
0191                 } else {
0192                     mainText = i18n("Choose screens to share with %1:", applicationName);
0193                 }
0194             } else {
0195                 if (appName.isEmpty()) {
0196                     mainText = i18n("Choose which screen to share with the requesting application:");
0197                 } else {
0198                     mainText = i18n("Choose which screen to share with %1:", applicationName);
0199                 }
0200             }
0201         }
0202     }
0203 
0204     // App only asked for windows
0205     else if (types & ScreenCastPortal::Window) {
0206         if (numberOfWindows == 1) {
0207             if (appName.isEmpty()) {
0208                 mainText = i18n("Share this window with the requesting application?");
0209             } else {
0210                 mainText = i18n("Share this window with %1?", applicationName);
0211             }
0212         } else {
0213             if (multiple) {
0214                 if (appName.isEmpty()) {
0215                     mainText = i18n("Choose windows to share with the requesting application:");
0216                 } else {
0217                     mainText = i18n("Choose windows to share with %1:", applicationName);
0218                 }
0219             } else {
0220                 if (appName.isEmpty()) {
0221                     mainText = i18n("Choose which window to share with the requesting application:");
0222                 } else {
0223                     mainText = i18n("Choose which window to share with %1:", applicationName);
0224                 }
0225             }
0226         }
0227     }
0228     props.insert("mainText", mainText);
0229 
0230     create(QStringLiteral("qrc:/ScreenChooserDialog.qml"), props);
0231     connect(m_theDialog, SIGNAL(clearSelection()), this, SIGNAL(clearSelection()));
0232 }
0233 
0234 ScreenChooserDialog::~ScreenChooserDialog() = default;
0235 
0236 QList<Output> ScreenChooserDialog::selectedOutputs() const
0237 {
0238     OutputsModel *model = dynamic_cast<OutputsModel *>(m_theDialog->property("outputsModel").value<QObject *>());
0239     if (!model) {
0240         return {};
0241     }
0242     return model->selectedOutputs();
0243 }
0244 
0245 QList<QMap<int, QVariant>> ScreenChooserDialog::selectedWindows() const
0246 {
0247     FilteredWindowModel *model = dynamic_cast<FilteredWindowModel *>(m_theDialog->property("windowsModel").value<QObject *>());
0248     if (!model) {
0249         return {};
0250     }
0251     return model->selectedWindows();
0252 }
0253 
0254 QRect ScreenChooserDialog::selectedRegion() const
0255 {
0256     return m_region;
0257 }
0258 
0259 void ScreenChooserDialog::setRegion(const QRect region)
0260 {
0261     m_region = region;
0262 }
0263 
0264 bool ScreenChooserDialog::allowRestore() const
0265 {
0266     return m_theDialog->property("allowRestore").toBool();
0267 }
0268 
0269 void ScreenChooserDialog::accept()
0270 {
0271     bool valid = true;
0272 
0273     for (const auto &output : selectedOutputs()) {
0274         if (output.outputType() == Output::OutputType::Region) {
0275             QScopedPointer<SelectionEditor, QScopedPointerDeleteLater> selectionEditor(new SelectionEditor(this));
0276 
0277             valid = selectionEditor->exec();
0278             setRegion(selectionEditor->rect());
0279 
0280             break;
0281         }
0282     }
0283 
0284     if (!valid) {
0285         // if we selected rectangular region, but didn't actually choose a region, start over
0286         QTimer::singleShot(0, m_theDialog, SLOT(present()));
0287     } else {
0288         QuickDialog::accept();
0289     }
0290 }
0291 
0292 #include "screenchooserdialog.moc"