diff --git a/libtaskmanager/taskmanagerrulesrc b/libtaskmanager/taskmanagerrulesrc --- a/libtaskmanager/taskmanagerrulesrc +++ b/libtaskmanager/taskmanagerrulesrc @@ -11,3 +11,4 @@ [Settings] MatchCommandLineFirst=perl TryIgnoreRuntimes=perl +SkipTaskbar=Soffice diff --git a/libtaskmanager/tasksmodel.cpp b/libtaskmanager/tasksmodel.cpp --- a/libtaskmanager/tasksmodel.cpp +++ b/libtaskmanager/tasksmodel.cpp @@ -73,6 +73,7 @@ bool launcherCheckNeeded = false; QList sortedPreFilterRows; QVector sortRowInsertQueue; + bool sortRowInsertQueueStale = false; QHash activityTaskCounts; static ActivityInfo* activityInfo; static int activityInfoUsers; @@ -191,6 +192,18 @@ if (roles.contains(AbstractTasksModel::IsActive)) { emit q->activeTaskChanged(); } + + // In manual sort mode, updateManualSortMap() may consult the sortRowInsertQueue + // for new tasks to sort in. Hidden tasks remain in the queue to potentially sort + // them later, when they are are actually revealed to the user. + // This is particularly useful in concert with taskmanagerrulesrc's SkipTaskbar + // key, which is used to hide window tasks which update from bogus to useful + // window metadata early in startup. The role change then coincides with positive + // app identication, which is when updateManualSortMap() becomes able to sort the + // task adjacent to its launcher when required to do so. + if (sortMode == SortManual && roles.contains(AbstractTasksModel::SkipTaskbar)) { + updateManualSortMap(); + } } ); @@ -228,6 +241,11 @@ sortedPreFilterRows.append(i); if (!separateLaunchers) { + if (sortRowInsertQueueStale) { + sortRowInsertQueue.clear(); + sortRowInsertQueueStale = false; + } + sortRowInsertQueue.append(sortedPreFilterRows.count() - 1); } } @@ -256,6 +274,11 @@ return; } + if (sortRowInsertQueueStale) { + sortRowInsertQueue.clear(); + sortRowInsertQueueStale = false; + } + for (int i = first; i <= last; ++i) { sortedPreFilterRows.removeOne(i); } @@ -539,71 +562,101 @@ // Otherwise process any entries in the insert queue and move them intelligently // in the sort map. } else { - while (sortRowInsertQueue.count()) { - const int row = sortRowInsertQueue.takeFirst(); - const QModelIndex &idx = concatProxyModel->index(sortedPreFilterRows.at(row), 0); + QMutableVectorIterator i(sortRowInsertQueue); + + while (i.hasNext()) { + i.next(); + + const int row = i.value(); + const QModelIndex &idx = concatProxyModel->index(sortedPreFilterRows.at(row), 0); + + // If a window task is currently hidden, we may want to keep it in the queue + // to sort it in later once it gets revealed. + // This is important in concert with taskmanagerrulesrc's SkipTaskbar key, which + // is used to hide window tasks which update from bogus to useful window metadata + // early in startup. Once the task no longer uses bogus metadata listed in the + // config key, its SkipTaskbar role changes to false, and then is it possible to + // sort the task adjacent to its launcher in the code below. + if (idx.data(AbstractTasksModel::IsWindow).toBool() && idx.data(AbstractTasksModel::SkipTaskbar).toBool()) { + // Since we're going to keep a row in the queue for now, make sure to + // mark the queue as stale so it's cleared on appends or row removals + // when they follow this sorting attempt. This frees us from having to + // update the indices in the queue to keep them valid. + // This means windowing system changes such as the opening or closing + // of a window task which happen during the time period that a window + // task has known bogus metadata, can upset what we're trying to + // achieve with this exception. However, due to the briefness of the + // time period and usage patterns, this is improbable, making this + // likely good enough. If it turns out not to be, this decision may be + // revisited later. + sortRowInsertQueueStale = true; + + break; + } else { + i.remove(); + } - bool moved = false; + bool moved = false; - // Try to move the task up to its right-most app sibling, unless this - // is us sorting in a launcher list for the first time. - if (launchersEverSet && !idx.data(AbstractTasksModel::IsLauncher).toBool()) { - for (int i = (row - 1); i >= 0; --i) { - const QModelIndex &concatProxyIndex = concatProxyModel->index(sortedPreFilterRows.at(i), 0); + // Try to move the task up to its right-most app sibling, unless this + // is us sorting in a launcher list for the first time. + if (launchersEverSet && !idx.data(AbstractTasksModel::IsLauncher).toBool()) { + for (int i = (row - 1); i >= 0; --i) { + const QModelIndex &concatProxyIndex = concatProxyModel->index(sortedPreFilterRows.at(i), 0); - if (appsMatch(concatProxyIndex, idx)) { - // Our sort map contains row indices prior to any filtering, but we don't - // want to sort new tasks in next to siblings we're filtering out higher up - // in the proxy chain, so check in with the filter model. - const QModelIndex &filterProxyIndex = filterProxyModel->mapFromSource(concatProxyIndex); + if (appsMatch(concatProxyIndex, idx)) { + // Our sort map contains row indices prior to any filtering, but we don't + // want to sort new tasks in next to siblings we're filtering out higher up + // in the proxy chain, so check in with the filter model. + const QModelIndex &filterProxyIndex = filterProxyModel->mapFromSource(concatProxyIndex); - if (filterProxyIndex.isValid()) { + if (filterProxyIndex.isValid()) { sortedPreFilterRows.move(row, i + 1); moved = true; - } + } - break; - } - } - } + break; + } + } + } - int insertPos = 0; + int insertPos = 0; - // If unsuccessful or skipped, and the new task is a launcher, put after - // the rightmost launcher or launcher-backed task in the map, or failing - // that at the start of the map. - if (!moved && idx.data(AbstractTasksModel::IsLauncher).toBool()) { - for (int i = 0; i < row; ++i) { - const QModelIndex &concatProxyIndex = concatProxyModel->index(sortedPreFilterRows.at(i), 0); + // If unsuccessful or skipped, and the new task is a launcher, put after + // the rightmost launcher or launcher-backed task in the map, or failing + // that at the start of the map. + if (!moved && idx.data(AbstractTasksModel::IsLauncher).toBool()) { + for (int i = 0; i < row; ++i) { + const QModelIndex &concatProxyIndex = concatProxyModel->index(sortedPreFilterRows.at(i), 0); - if (concatProxyIndex.data(AbstractTasksModel::IsLauncher).toBool() + if (concatProxyIndex.data(AbstractTasksModel::IsLauncher).toBool() || launcherTasksModel->launcherPosition(concatProxyIndex.data(AbstractTasksModel::LauncherUrlWithoutIcon).toUrl()) != -1) { insertPos = i + 1; - } else { + } else { break; - } - } - - sortedPreFilterRows.move(row, insertPos); - moved = true; - } - - // If we sorted in a launcher and it's the first time we're sorting in a - // launcher list, move existing windows to the launcher position now. - if (moved && !launchersEverSet) { - for (int i = (sortedPreFilterRows.count() - 1); i >= 0; --i) { - const QModelIndex &concatProxyIndex = concatProxyModel->index(sortedPreFilterRows.at(i), 0); - - if (!concatProxyIndex.data(AbstractTasksModel::IsLauncher).toBool() - && idx.data(AbstractTasksModel::LauncherUrlWithoutIcon) == concatProxyIndex.data(AbstractTasksModel::LauncherUrlWithoutIcon)) { - sortedPreFilterRows.move(i, insertPos); - - if (insertPos > i) { - --insertPos; - } - } - } - } + } + } + + sortedPreFilterRows.move(row, insertPos); + moved = true; + } + + // If we sorted in a launcher and it's the first time we're sorting in a + // launcher list, move existing windows to the launcher position now. + if (moved && !launchersEverSet) { + for (int i = (sortedPreFilterRows.count() - 1); i >= 0; --i) { + const QModelIndex &concatProxyIndex = concatProxyModel->index(sortedPreFilterRows.at(i), 0); + + if (!concatProxyIndex.data(AbstractTasksModel::IsLauncher).toBool() + && idx.data(AbstractTasksModel::LauncherUrlWithoutIcon) == concatProxyIndex.data(AbstractTasksModel::LauncherUrlWithoutIcon)) { + sortedPreFilterRows.move(i, insertPos); + + if (insertPos > i) { + --insertPos; + } + } + } + } } } } diff --git a/libtaskmanager/tasktools.h b/libtaskmanager/tasktools.h --- a/libtaskmanager/tasktools.h +++ b/libtaskmanager/tasktools.h @@ -40,6 +40,7 @@ QString genericName; // Generic application name. QIcon icon; QUrl url; + bool skipTaskbar = false; }; enum UrlComparisonMode { diff --git a/libtaskmanager/tasktools.cpp b/libtaskmanager/tasktools.cpp --- a/libtaskmanager/tasktools.cpp +++ b/libtaskmanager/tasktools.cpp @@ -65,6 +65,11 @@ pixmap.loadFromData(bytes); data.icon.addPixmap(pixmap); } + + if (uQuery.hasQueryItem(QLatin1String("skipTaskbar"))) { + QString skipTaskbar(uQuery.queryItemValue(QLatin1String("skipTaskbar"))); + data.skipTaskbar = (skipTaskbar == QStringLiteral("true")); + } } // applications: URLs are used to refer to applications by their KService::menuId @@ -405,6 +410,23 @@ services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Name) and (not exist NoDisplay or not NoDisplay)").arg(appId)); sortServicesByMenuId(services, appId); } + + // Check rules configuration for whether we want to hide this task. + // Some window tasks update from bogus to useful metadata early during startup. + // This config key allows listing the bogus metadata, and the matching window + // tasks are hidden until they perform a metadate update that stops them from + // matching. + QStringList skipTaskbar = set.readEntry("SkipTaskbar", QStringList()); + + if (skipTaskbar.contains(appId)) { + QUrlQuery query(url); + query.addQueryItem(QStringLiteral("skipTaskbar"), QStringLiteral("true")); + url.setQuery(query); + } else if (skipTaskbar.contains(mapped)) { + QUrlQuery query(url); + query.addQueryItem(QStringLiteral("skipTaskbar"), QStringLiteral("true")); + url.setQuery(query); + } } // Ok, absolute *last* chance, try matching via pid (but only if we have not already tried this!) ... @@ -451,7 +473,8 @@ // applications: URLs are used to refer to applications by their KService::menuId // (i.e. .desktop file name) rather than the absolute path to a .desktop file. if (!menuId.isEmpty()) { - return QUrl(QStringLiteral("applications:") + menuId); + url.setUrl(QStringLiteral("applications:") + menuId); + return url; } QString path = services.at(0)->entryPath(); @@ -461,7 +484,10 @@ } if (!path.isEmpty()) { + QString query = url.query(); url = QUrl::fromLocalFile(path); + url.setQuery(query); + return url; } } diff --git a/libtaskmanager/waylandtasksmodel.cpp b/libtaskmanager/waylandtasksmodel.cpp --- a/libtaskmanager/waylandtasksmodel.cpp +++ b/libtaskmanager/waylandtasksmodel.cpp @@ -86,7 +86,8 @@ QVector{Qt::DecorationRole, AbstractTasksModel::AppId, AbstractTasksModel::AppName, AbstractTasksModel::GenericName, AbstractTasksModel::LauncherUrl, - AbstractTasksModel::LauncherUrlWithoutIcon}); + AbstractTasksModel::LauncherUrlWithoutIcon, + AbstractTasksModel::SkipTaskbar}); }; rulesConfig = KSharedConfig::openConfig(QStringLiteral("taskmanagerrulesrc")); @@ -203,7 +204,7 @@ // Refresh roles satisfied from the app data cache. this->dataChanged(window, QVector{AppId, AppName, GenericName, - LauncherUrl, LauncherUrlWithoutIcon}); + LauncherUrl, LauncherUrlWithoutIcon, SkipTaskbar}); } ); @@ -405,7 +406,7 @@ } else if (role == IsDemandingAttention) { return window->isDemandingAttention(); } else if (role == SkipTaskbar) { - return window->skipTaskbar(); + return window->skipTaskbar() || d->appData(window).skipTaskbar; } else if (role == SkipPager) { // FIXME Implement. } else if (role == AppPid) { diff --git a/libtaskmanager/xwindowtasksmodel.cpp b/libtaskmanager/xwindowtasksmodel.cpp --- a/libtaskmanager/xwindowtasksmodel.cpp +++ b/libtaskmanager/xwindowtasksmodel.cpp @@ -116,7 +116,8 @@ QVector{Qt::DecorationRole, AbstractTasksModel::AppId, AbstractTasksModel::AppName, AbstractTasksModel::GenericName, AbstractTasksModel::LauncherUrl, - AbstractTasksModel::LauncherUrlWithoutIcon}); + AbstractTasksModel::LauncherUrlWithoutIcon, + AbstractTasksModel::SkipTaskbar}); }; sycocaChangeTimer.setSingleShot(true); @@ -330,7 +331,7 @@ || properties2 & (NET::WM2DesktopFileName | NET::WM2WindowClass)) { wipeInfoCache = true; wipeAppDataCache = true; - changedRoles << Qt::DecorationRole << AppId << AppName << GenericName << LauncherUrl << AppPid; + changedRoles << Qt::DecorationRole << AppId << AppName << GenericName << LauncherUrl << AppPid << SkipTaskbar; } if (properties & (NET::WMName | NET::WMVisibleName)) { @@ -645,7 +646,9 @@ const KWindowInfo *info = d->windowInfo(window); // _NET_WM_WINDOW_TYPE_UTILITY type windows should not be on task bars, // but they should be shown on pagers. - return (info->hasState(NET::SkipTaskbar) || info->windowType(NET::UtilityMask) == NET::Utility); + return (info->hasState(NET::SkipTaskbar) + || info->windowType(NET::UtilityMask) == NET::Utility + || d->appData(window).skipTaskbar); } else if (role == SkipPager) { return d->windowInfo(window)->hasState(NET::SkipPager); } else if (role == AppPid) {