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"