Phosphor
Qt6 / Wayland library suite for window-management tools
 
Loading...
Searching...
No Matches
WindowId.h
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 fuddlesworth
2// SPDX-License-Identifier: LGPL-2.1-or-later
3
4#pragma once
5
6// PhosphorIdentity is an INTERFACE library — no generated export header.
7// Every helper below is `inline` and lives entirely in this header.
8
9#include <QString>
10#include <QStringView>
11
12namespace PhosphorIdentity {
13
23namespace WindowId {
24
39inline QString buildCompositeId(QStringView appId, QStringView instanceId)
40{
41 if (instanceId.isEmpty()) {
42 return appId.toString();
43 }
44 QString out;
45 out.reserve(appId.size() + 1 + instanceId.size());
46 out.append(appId);
47 out.append(QLatin1Char('|'));
48 out.append(instanceId);
49 return out;
50}
51
56inline QString extractAppId(const QString& windowId)
57{
58 if (windowId.isEmpty()) {
59 return windowId;
60 }
61 int sep = windowId.indexOf(QLatin1Char('|'));
62 return (sep >= 0) ? windowId.left(sep) : windowId;
63}
64
75inline QString extractInstanceId(const QString& windowId)
76{
77 if (windowId.isEmpty()) {
78 return windowId;
79 }
80 int sep = windowId.indexOf(QLatin1Char('|'));
81 return (sep >= 0) ? windowId.mid(sep + 1) : windowId;
82}
83
100inline QString normalizeAppId(const QString& desktopFileName, const QString& windowClass)
101{
102 QString appId = desktopFileName.trimmed();
103 if (appId.isEmpty()) {
104 // Trim the whole class BEFORE the split: a class with trailing
105 // whitespace ("resourceName resourceClass ") would otherwise split at
106 // the trailing space, yield an empty token, and drop an otherwise
107 // valid identity.
108 const QString trimmedClass = windowClass.trimmed();
109 const int sep = trimmedClass.lastIndexOf(QLatin1Char(' '));
110 appId = (sep >= 0 ? trimmedClass.mid(sep + 1) : trimmedClass);
111 }
112 return appId.toLower();
113}
114
125inline bool isValidAppId(const QString& appId)
126{
127 if (appId.isEmpty()) {
128 return false;
129 }
130 for (const QChar c : appId) {
131 if (c.isSpace()) {
132 return false;
133 }
134 }
135 return true;
136}
137
148inline QString deriveShortName(const QString& windowClass)
149{
150 if (windowClass.isEmpty()) {
151 return QString();
152 }
153 // Strip a trailing dot first so "org.kde.foo." behaves the same as
154 // "org.kde.foo" — the dot would otherwise short-circuit the segment
155 // extraction below into returning the original string verbatim.
156 QStringView trimmed(windowClass);
157 while (!trimmed.isEmpty() && trimmed.back() == QLatin1Char('.')) {
158 trimmed.chop(1);
159 }
160 if (trimmed.isEmpty()) {
161 return QString();
162 }
163 const int dotIdx = trimmed.lastIndexOf(QLatin1Char('.'));
164 if (dotIdx >= 0 && dotIdx < trimmed.length() - 1) {
165 return trimmed.mid(dotIdx + 1).toString();
166 }
167 return trimmed.toString();
168}
169
185inline bool appIdMatches(const QString& appId, const QString& pattern)
186{
187 if (appId.isEmpty() || pattern.isEmpty()) {
188 return false;
189 }
190 if (appId.compare(pattern, Qt::CaseInsensitive) == 0) {
191 return true;
192 }
193 // Trailing dot-segment: "org.mozilla.firefox" ends with ".firefox"
194 if (appId.length() > pattern.length() + 1 && appId[appId.length() - pattern.length() - 1] == QLatin1Char('.')
195 && appId.endsWith(pattern, Qt::CaseInsensitive)) {
196 return true;
197 }
198 // Reverse: appId is a trailing dot-segment of pattern
199 if (pattern.length() > appId.length() + 1 && pattern[pattern.length() - appId.length() - 1] == QLatin1Char('.')
200 && pattern.endsWith(appId, Qt::CaseInsensitive)) {
201 return true;
202 }
203 // Last-segment prefix: pattern "systemsettings" matches start of appId's
204 // last segment "systemsettings5". Here `pattern` is the prefix candidate,
205 // so gate on its length.
206 if (pattern.length() >= 5) {
207 const int lastDot = appId.lastIndexOf(QLatin1Char('.'));
208 if (lastDot >= 0) {
209 QStringView lastSeg = QStringView(appId).mid(lastDot + 1);
210 if (lastSeg.startsWith(pattern, Qt::CaseInsensitive) && lastSeg.length() != pattern.length()) {
211 return true;
212 }
213 }
214 }
215 // Reverse: appId matches start of pattern's last segment. Here `appId`
216 // is the prefix candidate, so gate on its length (not pattern's).
217 if (appId.length() >= 5) {
218 const int lastDot = pattern.lastIndexOf(QLatin1Char('.'));
219 if (lastDot >= 0) {
220 QStringView lastSeg = QStringView(pattern).mid(lastDot + 1);
221 if (lastSeg.startsWith(appId, Qt::CaseInsensitive) && lastSeg.length() != appId.length()) {
222 return true;
223 }
224 }
225 }
226 return false;
227}
228
229} // namespace WindowId
230} // namespace PhosphorIdentity
QString deriveShortName(const QString &windowClass)
Derive short name from app ID for icon/app display Reverse-DNS: "org.kde.dolphin" → last dot-segment ...
Definition WindowId.h:148
QString buildCompositeId(QStringView appId, QStringView instanceId)
Build a composite window id from its appId + instance parts.
Definition WindowId.h:39
bool appIdMatches(const QString &appId, const QString &pattern)
Segment-aware app ID matching for exclusion lists.
Definition WindowId.h:185
QString normalizeAppId(const QString &desktopFileName, const QString &windowClass)
Derive a canonical appId from a window's desktop-file name and class.
Definition WindowId.h:100
QString extractInstanceId(const QString &windowId)
Extract the stable KWin instance identifier (UUID) from a full window ID.
Definition WindowId.h:75
bool isValidAppId(const QString &appId)
Whether appId is a well-formed canonical app identifier.
Definition WindowId.h:125
QString extractAppId(const QString &windowId)
Extract app identity from window ID (portion before the '|' separator) Format: "appId|internalUuid" →...
Definition WindowId.h:56
Definition ScreenId.h:26