File indexing completed on 2024-04-28 16:55:47

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 
0012 #include <KLocalizedString>
0013 #include <KWayland/Client/plasmawindowmanagement.h>
0014 #include <KWayland/Client/plasmawindowmodel.h>
0015 
0016 #include <QCoreApplication>
0017 #include <QSettings>
0018 #include <QSortFilterProxyModel>
0019 #include <QStandardPaths>
0020 #include <QWindow>
0021 
0022 class FilteredWindowModel : public QSortFilterProxyModel
0023 {
0024     Q_OBJECT
0025     Q_PROPERTY(bool hasSelection READ hasSelection NOTIFY hasSelectionChanged)
0026 public:
0027     FilteredWindowModel(QObject *parent)
0028         : QSortFilterProxyModel(parent)
0029     {
0030     }
0031 
0032     bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
0033     {
0034         if (source_parent.isValid())
0035             return false;
0036 
0037         const auto idx = sourceModel()->index(source_row, 0);
0038         using KWayland::Client::PlasmaWindowModel;
0039 
0040         return !idx.data(PlasmaWindowModel::SkipTaskbar).toBool() //
0041             && !idx.data(PlasmaWindowModel::SkipSwitcher).toBool() //
0042             && idx.data(PlasmaWindowModel::Pid) != QCoreApplication::applicationPid();
0043     }
0044 
0045     QMap<int, QVariant> itemData(const QModelIndex &index) const override
0046     {
0047         using KWayland::Client::PlasmaWindowModel;
0048         auto ret = QSortFilterProxyModel::itemData(index);
0049         for (int i = PlasmaWindowModel::AppId; i <= PlasmaWindowModel::Uuid; ++i) {
0050             ret[i] = index.data(i);
0051         }
0052         return ret;
0053     }
0054 
0055     bool setData(const QModelIndex &index, const QVariant &value, int role) override
0056     {
0057         if (!checkIndex(index, CheckIndexOption::IndexIsValid) || role != Qt::CheckStateRole) {
0058             return false;
0059         }
0060 
0061         using KWayland::Client::PlasmaWindowModel;
0062         const QString uuid = index.data(PlasmaWindowModel::Uuid).toString();
0063         if (value == Qt::Checked) {
0064             m_selected.insert(index);
0065         } else {
0066             m_selected.remove(index);
0067         }
0068         Q_EMIT dataChanged(index, index, {role});
0069         if (m_selected.count() <= 1) {
0070             Q_EMIT hasSelectionChanged();
0071         }
0072         return true;
0073     }
0074 
0075     QVector<QMap<int, QVariant>> selectedWindows() const
0076     {
0077         QVector<QMap<int, QVariant>> ret;
0078         ret.reserve(m_selected.size());
0079         for (const auto &index : m_selected) {
0080             if (index.isValid())
0081                 ret << itemData(index);
0082         }
0083         return ret;
0084     }
0085 
0086     QHash<int, QByteArray> roleNames() const override
0087     {
0088         QHash<int, QByteArray> ret = sourceModel()->roleNames();
0089         ret.insert(Qt::CheckStateRole, "checked");
0090         return ret;
0091     }
0092 
0093     QVariant data(const QModelIndex &index, int role) const override
0094     {
0095         if (!checkIndex(index, CheckIndexOption::IndexIsValid)) {
0096             return {};
0097         }
0098 
0099         switch (role) {
0100         case Qt::CheckStateRole:
0101             return m_selected.contains(index) ? Qt::Checked : Qt::Unchecked;
0102         default:
0103             return QSortFilterProxyModel::data(index, role);
0104         }
0105         return {};
0106     }
0107 
0108     bool hasSelection()
0109     {
0110         return !m_selected.isEmpty();
0111     }
0112 
0113     void clearSelection()
0114     {
0115         auto selected = m_selected;
0116         m_selected.clear();
0117 
0118         for (const auto &index : selected) {
0119             if (index.isValid())
0120                 Q_EMIT dataChanged(index, index, {Qt::CheckStateRole});
0121         }
0122         Q_EMIT hasSelectionChanged();
0123     }
0124 
0125 Q_SIGNALS:
0126     void hasSelectionChanged();
0127 
0128 private:
0129     QSet<QPersistentModelIndex> m_selected;
0130 };
0131 
0132 ScreenChooserDialog::ScreenChooserDialog(const QString &appName, bool multiple, ScreenCastPortal::SourceTypes types)
0133     : QuickDialog()
0134 {
0135     Q_ASSERT(types != 0);
0136 
0137     QVariantMap props = {
0138         {"title", i18n("Screen Sharing")},
0139         {"multiple", multiple},
0140     };
0141 
0142     int numberOfMonitors = 0;
0143     if (types & ScreenCastPortal::Monitor) {
0144         auto model = new OutputsModel(OutputsModel::Options(OutputsModel::WorkspaceIncluded | OutputsModel::VirtualIncluded), this);
0145         props.insert("outputsModel", QVariant::fromValue<QObject *>(model));
0146         numberOfMonitors += model->rowCount(QModelIndex());
0147         connect(this, &ScreenChooserDialog::clearSelection, model, &OutputsModel::clearSelection);
0148     }
0149 
0150     int numberOfWindows = 0;
0151     if (types & ScreenCastPortal::Window) {
0152         auto model = new KWayland::Client::PlasmaWindowModel(WaylandIntegration::plasmaWindowManagement());
0153         auto windowsProxy = new FilteredWindowModel(this);
0154         windowsProxy->setSourceModel(model);
0155         props.insert("windowsModel", QVariant::fromValue<QObject *>(windowsProxy));
0156         connect(this, &ScreenChooserDialog::clearSelection, windowsProxy, &FilteredWindowModel::clearSelection);
0157         numberOfWindows += model->rowCount(QModelIndex());
0158     }
0159 
0160     const QString applicationName = Utils::applicationName(appName);
0161 
0162     QString mainText;
0163 
0164     // App asked for monitors and windows
0165     if (types & ScreenCastPortal::Monitor && types & ScreenCastPortal::Window) {
0166         if (appName.isEmpty()) {
0167             mainText = i18n("Choose what to share with the requesting application:");
0168         } else {
0169             mainText = i18n("Choose what to share with %1:", applicationName);
0170         }
0171     }
0172 
0173     // App only asked for monitors
0174     else if (types & ScreenCastPortal::Monitor) {
0175         if (numberOfMonitors == 1) {
0176             if (appName.isEmpty()) {
0177                 mainText = i18n("Share this screen with the requesting application?");
0178             } else {
0179                 mainText = i18n("Share this screen with %1?", applicationName);
0180             }
0181         } else {
0182             if (multiple) {
0183                 if (appName.isEmpty()) {
0184                     mainText = i18n("Choose screens to share with the requesting application:");
0185                 } else {
0186                     mainText = i18n("Choose screens to share with %1:", applicationName);
0187                 }
0188             } else {
0189                 if (appName.isEmpty()) {
0190                     mainText = i18n("Choose which screen to share with the requesting application:");
0191                 } else {
0192                     mainText = i18n("Choose which screen to share with %1:", applicationName);
0193                 }
0194             }
0195         }
0196     }
0197 
0198     // App only asked for windows
0199     else if (types & ScreenCastPortal::Window) {
0200         if (numberOfWindows == 1) {
0201             if (appName.isEmpty()) {
0202                 mainText = i18n("Share this window with the requesting application?");
0203             } else {
0204                 mainText = i18n("Share this window with %1?", applicationName);
0205             }
0206         } else {
0207             if (multiple) {
0208                 if (appName.isEmpty()) {
0209                     mainText = i18n("Choose windows to share with the requesting application:");
0210                 } else {
0211                     mainText = i18n("Choose windows to share with %1:", applicationName);
0212                 }
0213             } else {
0214                 if (appName.isEmpty()) {
0215                     mainText = i18n("Choose which window to share with the requesting application:");
0216                 } else {
0217                     mainText = i18n("Choose which window to share with %1:", applicationName);
0218                 }
0219             }
0220         }
0221     }
0222     props.insert("mainText", mainText);
0223 
0224     create(QStringLiteral("qrc:/ScreenChooserDialog.qml"), props);
0225     connect(m_theDialog, SIGNAL(clearSelection()), this, SIGNAL(clearSelection()));
0226 }
0227 
0228 ScreenChooserDialog::~ScreenChooserDialog() = default;
0229 
0230 QList<Output> ScreenChooserDialog::selectedOutputs() const
0231 {
0232     OutputsModel *model = dynamic_cast<OutputsModel *>(m_theDialog->property("outputsModel").value<QObject *>());
0233     if (!model) {
0234         return {};
0235     }
0236     return model->selectedOutputs();
0237 }
0238 
0239 QVector<QMap<int, QVariant>> ScreenChooserDialog::selectedWindows() const
0240 {
0241     FilteredWindowModel *model = dynamic_cast<FilteredWindowModel *>(m_theDialog->property("windowsModel").value<QObject *>());
0242     if (!model) {
0243         return {};
0244     }
0245     return model->selectedWindows();
0246 }
0247 
0248 bool ScreenChooserDialog::allowRestore() const
0249 {
0250     return m_theDialog->property("allowRestore").toBool();
0251 }
0252 
0253 #include "screenchooserdialog.moc"