File indexing completed on 2024-06-16 04:41:28

0001 /*
0002     SPDX-FileCopyrightText: 2008 Urs Wolfer <uwolfer@kde.org>
0003     SPDX-FileCopyrightText: 2009 Tony Murray <murraytony@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "remotedesktopsmodel.h"
0009 #include "krdc_debug.h"
0010 
0011 #include <KFormat>
0012 #include <KLocalizedString>
0013 #ifdef BUILD_ZEROCONF
0014 #include <KDNSSD/RemoteService>
0015 #include <KDNSSD/ServiceBrowser>
0016 #include <kdnssd_version.h>
0017 #endif
0018 #include <QLoggingCategory>
0019 #include <QObject>
0020 #include <QStandardPaths>
0021 
0022 RemoteDesktopsModel::RemoteDesktopsModel(QObject *parent, KBookmarkManager *manager)
0023     : QAbstractTableModel(parent)
0024 {
0025     m_manager = manager;
0026     connect(m_manager, SIGNAL(changed(QString, QString)), SLOT(bookmarksChanged()));
0027     buildModelFromBookmarkGroup(m_manager->root());
0028 
0029 #ifdef BUILD_ZEROCONF
0030     // Add RDP and NX if they start announcing via Zeroconf:
0031     m_protocols[QLatin1String("_rfb._tcp")] = QLatin1String("vnc");
0032 
0033     zeroconfBrowser = new KDNSSD::ServiceBrowser(QLatin1String("_rfb._tcp"), true);
0034     connect(zeroconfBrowser, SIGNAL(finished()), this, SLOT(servicesChanged()));
0035     zeroconfBrowser->startBrowse();
0036     qCDebug(KRDC) << "Browsing for zeroconf hosts.";
0037 #endif
0038 }
0039 
0040 RemoteDesktopsModel::~RemoteDesktopsModel()
0041 {
0042 }
0043 
0044 int RemoteDesktopsModel::columnCount(const QModelIndex &) const
0045 {
0046     return 6; // same as count of RemoteDesktopsModel::DisplayItems enum
0047 }
0048 
0049 int RemoteDesktopsModel::rowCount(const QModelIndex &) const
0050 {
0051     return remoteDesktops.size();
0052 }
0053 
0054 Qt::ItemFlags RemoteDesktopsModel::flags(const QModelIndex &index) const
0055 {
0056     if (!index.isValid())
0057         return Qt::NoItemFlags;
0058     if (index.column() == RemoteDesktopsModel::Favorite) {
0059         return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
0060     }
0061     return Qt::ItemIsEnabled;
0062 }
0063 
0064 bool RemoteDesktopsModel::setData(const QModelIndex &index, const QVariant &value, int role)
0065 {
0066     if (index.isValid() && role == Qt::CheckStateRole && index.column() == RemoteDesktopsModel::Favorite) {
0067         bool checked = (Qt::CheckState)value.toUInt() == Qt::Checked;
0068         remoteDesktops[index.row()].favorite = checked;
0069 
0070         RemoteDesktop rd = remoteDesktops.at(index.row());
0071         if (checked) {
0072             KBookmarkGroup root = m_manager->root();
0073             root.addBookmark(rd.title, QUrl(rd.url), QString());
0074             m_manager->emitChanged(root);
0075         } else {
0076             BookmarkManager::removeByUrl(m_manager, rd.url, true, rd.title);
0077         }
0078         return true;
0079     }
0080     return false;
0081 }
0082 
0083 QVariant RemoteDesktopsModel::data(const QModelIndex &index, int role) const
0084 {
0085     if (!index.isValid())
0086         return QVariant();
0087 
0088     RemoteDesktop item = remoteDesktops.at(index.row());
0089 
0090     switch (role) {
0091     case Qt::DisplayRole:
0092         switch (index.column()) {
0093         case RemoteDesktopsModel::Favorite:
0094             return item.favorite;
0095         case RemoteDesktopsModel::Title:
0096             return item.title;
0097         case RemoteDesktopsModel::LastConnected:
0098             return QVariant(item.lastConnected);
0099         case RemoteDesktopsModel::VisitCount:
0100             return item.visits;
0101         case RemoteDesktopsModel::Created:
0102             if (item.created.isNull())
0103                 return QVariant();
0104             return QLocale().toString(item.created.toLocalTime(), QLocale::FormatType::ShortFormat);
0105         case RemoteDesktopsModel::Source:
0106             switch (item.source) {
0107             case RemoteDesktop::Bookmarks:
0108                 return i18nc("Where each displayed link comes from", "Bookmarks");
0109             case RemoteDesktop::History:
0110                 return i18nc("Where each displayed link comes from", "History");
0111             case RemoteDesktop::Zeroconf:
0112                 return i18nc("Where each displayed link comes from", "Zeroconf");
0113             case RemoteDesktop::None:
0114                 return i18nc("Where each displayed link comes from", "None");
0115             }
0116             // fall-through
0117         default:
0118             return QVariant();
0119         }
0120 
0121     case Qt::CheckStateRole:
0122         if (index.column() == RemoteDesktopsModel::Favorite)
0123             return item.favorite ? Qt::Checked : Qt::Unchecked;
0124         return QVariant();
0125 
0126     case Qt::ToolTipRole:
0127         switch (index.column()) {
0128         case RemoteDesktopsModel::Favorite:
0129             if (item.favorite) {
0130                 return i18nc("Remove the selected url from the bookarks menu", "Remove the bookmark for %1.", item.title);
0131             } else {
0132                 return i18nc("Add the selected url to the bookmarks menu", "Bookmark %1.", item.title);
0133             }
0134         case RemoteDesktopsModel::LastConnected:
0135             if (!item.lastConnected.isNull()) {
0136                 return QLocale().toString(item.lastConnected.toLocalTime(), QLocale::FormatType::LongFormat);
0137             }
0138             break; // else show default tooltip
0139         case RemoteDesktopsModel::Created:
0140             if (!item.created.isNull()) {
0141                 return QLocale().toString(item.lastConnected.toLocalTime(), QLocale::FormatType::LongFormat);
0142             }
0143             break; // else show default tooltip
0144         default:
0145             break;
0146         }
0147         return item.url; // use the url for the tooltip
0148 
0149     case 10001: // url for dockwidget
0150         return item.url;
0151 
0152     case 10002: // filter
0153         return QUrl::fromPercentEncoding(QString(item.url + item.title).toUtf8()); // return both user visible title and url data, percent encoded
0154 
0155     case 10003: // title for dockwidget
0156         return item.title;
0157 
0158     default:
0159         return QVariant();
0160     }
0161 }
0162 
0163 QVariant RemoteDesktopsModel::headerData(int section, Qt::Orientation orientation, int role) const
0164 {
0165     if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
0166         switch (section) {
0167         case RemoteDesktopsModel::Favorite:
0168             return QVariant(); // the favorite column is to small for a header
0169         case RemoteDesktopsModel::Title:
0170             return i18nc("Header of the connections list, title/url for remote connection", "Remote Desktop");
0171         case RemoteDesktopsModel::LastConnected:
0172             return i18nc("Header of the connections list, the last time this connection was initiated", "Last Connected");
0173         case RemoteDesktopsModel::VisitCount:
0174             return i18nc("Header of the connections list, the number of times this connection has been visited", "Visits");
0175         case RemoteDesktopsModel::Created:
0176             return i18nc("Header of the connections list, the time when this entry was created", "Created");
0177         case RemoteDesktopsModel::Source:
0178             return i18nc("Header of the connections list, where this entry comes from", "Source");
0179         }
0180     }
0181     return QVariant();
0182 }
0183 
0184 // does not trigger view update, you must do this by hand after using this function
0185 void RemoteDesktopsModel::removeAllItemsFromSources(RemoteDesktop::Sources sources)
0186 {
0187     QMutableListIterator<RemoteDesktop> iter(remoteDesktops);
0188     while (iter.hasNext()) {
0189         iter.next();
0190         // if it matches any of the specified sources, remove it
0191         if ((iter.value().source & sources) > 0)
0192             iter.remove();
0193     }
0194 }
0195 
0196 void RemoteDesktopsModel::bookmarksChanged()
0197 {
0198     removeAllItemsFromSources(RemoteDesktop::Bookmarks | RemoteDesktop::History);
0199     buildModelFromBookmarkGroup(m_manager->root());
0200     beginResetModel();
0201     endResetModel();
0202 }
0203 
0204 // Danger Will Roobinson, confusing code ahead!
0205 void RemoteDesktopsModel::buildModelFromBookmarkGroup(const KBookmarkGroup &group)
0206 {
0207     KBookmark bm = group.first();
0208     while (!bm.isNull()) {
0209         if (bm.isGroup()) {
0210             // recurse subfolders and treat it special if it is the history folder
0211             buildModelFromBookmarkGroup(bm.toGroup());
0212         } else { // not a group
0213 
0214             RemoteDesktop item;
0215             item.title = bm.fullText();
0216             item.url = bm.url().url();
0217             int index = remoteDesktops.indexOf(item); // search for this url to see if we need to update it
0218             bool newItem = index < 0; // do we need to create a new item?
0219 
0220             // we want to merge all copies of a url into one link, so if the item exists, update it
0221             if (group.metaDataItem(QLatin1String("krdc-history")) == QLatin1String("historyfolder")) {
0222                 // set source and favorite (will override later if needed)
0223                 item.source = RemoteDesktop::History;
0224                 item.favorite = false;
0225 
0226                 // since we are in the history folder collect statistics and add them
0227                 QDateTime connected = QDateTime();
0228                 QDateTime created = QDateTime();
0229                 bool ok = false;
0230                 // first the created datetime
0231                 created = QDateTime::fromSecsSinceEpoch(bm.metaDataItem(QLatin1String("time_added")).toLongLong(&ok));
0232                 if (ok)
0233                     (newItem ? item : remoteDesktops[index]).created = created;
0234                 // then the last visited datetime
0235                 ok = false;
0236                 connected = QDateTime::fromSecsSinceEpoch(bm.metaDataItem(QLatin1String("time_visited")).toLongLong(&ok));
0237                 if (ok)
0238                     (newItem ? item : remoteDesktops[index]).lastConnected = connected;
0239                 // finally the visited count
0240                 ok = false;
0241                 int visits = bm.metaDataItem(QLatin1String("visit_count")).toInt(&ok);
0242                 if (ok)
0243                     (newItem ? item : remoteDesktops[index]).visits = visits;
0244             } else {
0245                 if (newItem) {
0246                     // if this is a new item, just add the rest of the required data
0247                     item.lastConnected = QDateTime();
0248                     item.created = QDateTime();
0249                     item.visits = 0;
0250                     item.favorite = true;
0251                     item.source = RemoteDesktop::Bookmarks;
0252                 } else {
0253                     // otherwise override these fields with the info from the bookmark
0254                     remoteDesktops[index].title = bm.fullText();
0255                     remoteDesktops[index].favorite = true;
0256                     remoteDesktops[index].source = RemoteDesktop::Bookmarks;
0257                 }
0258             }
0259             // if we have a new item, add it
0260             if (newItem)
0261                 remoteDesktops.append(item);
0262         }
0263         bm = group.next(bm); // next item in the group
0264     }
0265 }
0266 
0267 #ifdef BUILD_ZEROCONF
0268 void RemoteDesktopsModel::servicesChanged()
0269 {
0270     // redo list because it is easier than finding and removing one that disappeared
0271     const QList<KDNSSD::RemoteService::Ptr> services = zeroconfBrowser->services();
0272     QUrl url;
0273     removeAllItemsFromSources(RemoteDesktop::Zeroconf);
0274     for (const KDNSSD::RemoteService::Ptr &service : services) {
0275         url.setScheme(m_protocols[service->type()].toLower());
0276         url.setHost(service->hostName());
0277         url.setPort(service->port());
0278 
0279         RemoteDesktop item;
0280         item.url = url.url();
0281 
0282         if (!remoteDesktops.contains(item)) {
0283             item.title = service->serviceName();
0284             item.source = RemoteDesktop::Zeroconf;
0285             item.created = QDateTime::currentDateTime();
0286             item.favorite = false;
0287             item.visits = 0;
0288             remoteDesktops.append(item);
0289         }
0290     }
0291     beginResetModel();
0292     endResetModel();
0293 }
0294 #endif