@@ -85,64 +85,71 @@ struct Locale {
85
85
QDebug operator <<(QDebug debug, const Locale& locale) {
86
86
auto saver = QDebugStateSaver (debug);
87
87
debug.nospace () << " Locale(language=" << locale.language << " , territory=" << locale.territory
88
- << " , modifier" << locale.modifier << ' )' ;
88
+ << " , modifier= " << locale.modifier << ' )' ;
89
89
90
90
return debug;
91
91
}
92
92
93
- void DesktopEntry::parseEntry (const QString& text) {
93
+ ParsedDesktopEntryData DesktopEntry::parseText (const QString& id, const QString& text) {
94
+ ParsedDesktopEntryData data;
95
+ data.id = id;
94
96
const auto & system = Locale::system ();
95
97
96
98
auto groupName = QString ();
97
99
auto entries = QHash<QString, QPair<Locale, QString>>();
98
100
99
- auto finishCategory = [this , &groupName, &entries]() {
101
+ auto finishCategory = [&data , &groupName, &entries]() {
100
102
if (groupName == " Desktop Entry" ) {
101
- if (entries[" Type" ].second != " Application" ) return ;
102
- if (entries.contains (" Hidden" ) && entries[" Hidden" ].second == " true" ) return ;
103
+ const auto typeIt = entries.constFind (" Type" );
104
+ if (typeIt == entries.cend () || typeIt->second != " Application" ) return ;
105
+
106
+ if (const auto hiddenIt = entries.constFind (" Hidden" );
107
+ hiddenIt != entries.cend () && hiddenIt->second == " true" )
108
+ return ;
103
109
104
110
for (const auto & [key, pair]: entries.asKeyValueRange ()) {
105
111
auto & [_, value] = pair;
106
- this -> mEntries .insert (key, value);
107
-
108
- if (key == " Name" ) this -> mName = value;
109
- else if (key == " GenericName" ) this -> mGenericName = value;
110
- else if (key == " StartupWMClass" ) this -> mStartupClass = value;
111
- else if (key == " NoDisplay" ) this -> mNoDisplay = value == " true" ;
112
- else if (key == " Comment" ) this -> mComment = value;
113
- else if (key == " Icon" ) this -> mIcon = value;
112
+ data. entries .insert (key, value);
113
+
114
+ if (key == " Name" ) data. name = value;
115
+ else if (key == " GenericName" ) data. genericName = value;
116
+ else if (key == " StartupWMClass" ) data. startupClass = value;
117
+ else if (key == " NoDisplay" ) data. noDisplay = value == " true" ;
118
+ else if (key == " Comment" ) data. comment = value;
119
+ else if (key == " Icon" ) data. icon = value;
114
120
else if (key == " Exec" ) {
115
- this -> mExecString = value;
116
- this -> mCommand = DesktopEntry::parseExecString (value);
117
- } else if (key == " Path" ) this -> mWorkingDirectory = value;
118
- else if (key == " Terminal" ) this -> mTerminal = value == " true" ;
119
- else if (key == " Categories" ) this -> mCategories = value.split (u' ;' , Qt::SkipEmptyParts);
120
- else if (key == " Keywords" ) this -> mKeywords = value.split (u' ;' , Qt::SkipEmptyParts);
121
+ data. execString = value;
122
+ data. command = DesktopEntry::parseExecString (value);
123
+ } else if (key == " Path" ) data. workingDirectory = value;
124
+ else if (key == " Terminal" ) data. terminal = value == " true" ;
125
+ else if (key == " Categories" ) data. categories = value.split (u' ;' , Qt::SkipEmptyParts);
126
+ else if (key == " Keywords" ) data. keywords = value.split (u' ;' , Qt::SkipEmptyParts);
121
127
}
122
128
} else if (groupName.startsWith (" Desktop Action " )) {
123
129
auto actionName = groupName.sliced (16 );
124
- auto * action = new DesktopAction (actionName, this );
130
+ auto action = DesktopActionData ();
131
+ action.id = actionName;
125
132
126
133
for (const auto & [key, pair]: entries.asKeyValueRange ()) {
127
134
const auto & [_, value] = pair;
128
- action-> mEntries .insert (key, value);
135
+ action. entries .insert (key, value);
129
136
130
- if (key == " Name" ) action-> mName = value;
131
- else if (key == " Icon" ) action-> mIcon = value;
137
+ if (key == " Name" ) action. name = value;
138
+ else if (key == " Icon" ) action. icon = value;
132
139
else if (key == " Exec" ) {
133
- action-> mExecString = value;
134
- action-> mCommand = DesktopEntry::parseExecString (value);
140
+ action. execString = value;
141
+ action. command = DesktopEntry::parseExecString (value);
135
142
}
136
143
}
137
144
138
- this -> mActions .insert (actionName, action);
145
+ data. actions .insert (actionName, action);
139
146
}
140
147
141
148
entries.clear ();
142
149
};
143
150
144
- for (auto & line: text.split (u' \n ' , Qt::SkipEmptyParts )) {
145
- if (line.startsWith (u' #' )) continue ;
151
+ for (auto & line: text.split (u' \n ' )) {
152
+ if (line.isEmpty () || line. startsWith (u' #' )) continue ;
146
153
147
154
if (line.startsWith (u' [' ) && line.endsWith (u' ]' )) {
148
155
finishCategory ();
@@ -160,7 +167,7 @@ void DesktopEntry::parseEntry(const QString& text) {
160
167
const auto & value = line.sliced (splitIdx + 1 );
161
168
162
169
auto localeIdx = key.indexOf (' [' );
163
- Locale locale;
170
+ auto locale = Locale () ;
164
171
if (localeIdx != -1 && localeIdx != key.length () - 1 ) {
165
172
locale = Locale (key.sliced (localeIdx + 1 , key.length () - localeIdx - 2 ));
166
173
key = key.sliced (0 , localeIdx);
@@ -181,6 +188,38 @@ void DesktopEntry::parseEntry(const QString& text) {
181
188
}
182
189
183
190
finishCategory ();
191
+ return data;
192
+ }
193
+
194
+ void DesktopEntry::applyParsedData (const ParsedDesktopEntryData& data) {
195
+ this ->mEntries = data.entries ;
196
+ this ->mName = data.name ;
197
+ this ->mGenericName = data.genericName ;
198
+ this ->mStartupClass = data.startupClass ;
199
+ this ->mNoDisplay = data.noDisplay ;
200
+ this ->mComment = data.comment ;
201
+ this ->mIcon = data.icon ;
202
+ this ->mExecString = data.execString ;
203
+ this ->mCommand = data.command ;
204
+ this ->mWorkingDirectory = data.workingDirectory ;
205
+ this ->mTerminal = data.terminal ;
206
+ this ->mCategories = data.categories ;
207
+ this ->mKeywords = data.keywords ;
208
+
209
+ for (auto * action: this ->mActions .values ()) {
210
+ action->deleteLater ();
211
+ }
212
+ this ->mActions .clear ();
213
+
214
+ for (const auto & [actionName, actionData]: data.actions .asKeyValueRange ()) {
215
+ auto * action = new DesktopAction (actionData.id , this );
216
+ action->mEntries = actionData.entries ;
217
+ action->mName = actionData.name ;
218
+ action->mIcon = actionData.icon ;
219
+ action->mExecString = actionData.execString ;
220
+ action->mCommand = actionData.command ;
221
+ this ->mActions .insert (actionName, action);
222
+ }
184
223
}
185
224
186
225
void DesktopEntry::execute () const {
@@ -193,8 +232,8 @@ bool DesktopEntry::noDisplay() const { return this->mNoDisplay; }
193
232
QVector<DesktopAction*> DesktopEntry::actions () const { return this ->mActions .values (); }
194
233
195
234
QVector<QString> DesktopEntry::parseExecString (const QString& execString) {
196
- QVector<QString> arguments ;
197
- QString currentArgument;
235
+ auto arguments = QVector<QString>() ;
236
+ auto currentArgument = QString () ;
198
237
auto parsingString = false ;
199
238
auto escape = 0 ;
200
239
auto percent = false ;
@@ -257,7 +296,7 @@ QVector<QString> DesktopEntry::parseExecString(const QString& execString) {
257
296
}
258
297
259
298
void DesktopEntry::doExec (const QList<QString>& execString, const QString& workingDirectory) {
260
- qs::io::process::ProcessContext ctx ;
299
+ auto ctx = qs::io::process::ProcessContext () ;
261
300
ctx.setCommand (execString);
262
301
ctx.setWorkingDirectory (workingDirectory);
263
302
QuickshellGlobal::execDetached (ctx);
@@ -272,10 +311,11 @@ DesktopEntryScanner::DesktopEntryScanner(DesktopEntryManager* manager): manager(
272
311
}
273
312
274
313
void DesktopEntryScanner::run () {
275
- auto desktopPaths = manager-> monitor -> getDesktopDirectories ();
314
+ auto desktopPaths = DesktopEntryManager::desktopPaths ();
276
315
auto scanResults = DesktopEntryScanResults ();
277
316
278
- for (auto & path: std::ranges::reverse_view (desktopPaths)) {
317
+ for (int i = desktopPaths.size () - 1 ; i >= 0 ; --i) {
318
+ const auto & path = desktopPaths.at (i);
279
319
auto file = QFileInfo (path);
280
320
if (!file.isDir ()) continue ;
281
321
@@ -284,9 +324,9 @@ void DesktopEntryScanner::run() {
284
324
285
325
QMetaObject::invokeMethod (
286
326
this ->manager ,
287
- " onScanCompleted" ,
327
+ &DesktopEntryManager:: onScanCompleted,
288
328
Qt::QueuedConnection,
289
- Q_ARG (DesktopEntryScanResults, scanResults)
329
+ scanResults
290
330
);
291
331
}
292
332
@@ -309,19 +349,19 @@ void DesktopEntryScanner::scanDirectory(const QDir& dir, DesktopEntryScanResults
309
349
continue ;
310
350
}
311
351
312
- auto id = manager-> extractIdFromPath (entry.absoluteFilePath ());
352
+ auto id = DesktopEntryManager:: extractIdFromPath (entry.absoluteFilePath ());
313
353
auto content = QString::fromUtf8 (file.readAll ());
314
354
315
- ParsedDesktopEntry parsed;
316
- parsed.id = id;
317
- parsed.content = std::move (content);
318
-
319
- entries.append (std::move (parsed));
355
+ auto data = DesktopEntry::parseText (id, content);
356
+ entries.append (std::move (data));
320
357
}
321
358
}
322
359
}
323
360
324
361
DesktopEntryManager::DesktopEntryManager () {
362
+ qRegisterMetaType<ParsedDesktopEntryData>(" ParsedDesktopEntryData" );
363
+ qRegisterMetaType<DesktopEntryScanResults>(" DesktopEntryScanResults" );
364
+
325
365
this ->monitor = new DesktopEntryMonitor (this );
326
366
connect (
327
367
this ->monitor ,
@@ -334,26 +374,16 @@ DesktopEntryManager::DesktopEntryManager() {
334
374
}
335
375
336
376
void DesktopEntryManager::scanDesktopEntries () {
337
- auto desktopPaths = this ->monitor ->getDesktopDirectories ();
338
- auto scanResults = DesktopEntryScanResults ();
339
-
340
- qCDebug (logDesktopEntry) << " Creating desktop entry scanners" ;
341
-
342
- auto scanner = DesktopEntryScanner (this );
343
- for (auto & path: std::ranges::reverse_view (desktopPaths)) {
344
- auto file = QFileInfo (path);
377
+ qCDebug (logDesktopEntry) << " Starting desktop entry scan" ;
345
378
346
- if (!file.isDir ()) {
347
- qCDebug (logDesktopEntry) << " Not scanning path" << path << " as it is not a directory" ;
348
- continue ;
349
- }
350
-
351
- qCDebug (logDesktopEntry) << " Scanning path" << path;
352
- scanner.scanDirectory (QDir (path), scanResults);
379
+ if (this ->scanInProgress ) {
380
+ qCDebug (logDesktopEntry) << " Scan already in progress, skipping" ;
381
+ return ;
353
382
}
354
383
355
- this ->onScanCompleted (scanResults);
356
- this ->scanInProgress = false ;
384
+ this ->scanInProgress = true ;
385
+ auto * scanner = new DesktopEntryScanner (this );
386
+ QThreadPool::globalInstance ()->start (scanner);
357
387
}
358
388
359
389
DesktopEntryManager* DesktopEntryManager::instance () {
@@ -406,21 +436,57 @@ void DesktopEntryManager::handleFileChanges() {
406
436
}
407
437
408
438
QString DesktopEntryManager::extractIdFromPath (const QString& path) {
409
- auto info = QFileInfo (path);
410
- auto id = info.completeBaseName ();
439
+ const auto fi = QFileInfo (path);
440
+ const auto canon = fi.canonicalFilePath ();
441
+ const auto actual = canon.isEmpty () ? fi.absoluteFilePath () : canon;
442
+ for (const auto & rootPath: DesktopEntryManager::desktopPaths ()) {
443
+ auto root = QDir (rootPath);
444
+ const auto rel = root.relativeFilePath (actual);
445
+ if (!rel.startsWith (" ../" )) {
446
+ auto relInfo = QFileInfo (rel);
447
+ auto id = QString ();
448
+ if (relInfo.path () != " ." && !relInfo.path ().isEmpty ()) {
449
+ id = relInfo.path ();
450
+ id.replace (' /' , ' -' );
451
+ id += ' -' + relInfo.completeBaseName ();
452
+ } else {
453
+ id = relInfo.completeBaseName ();
454
+ }
455
+ return id;
456
+ }
457
+ }
458
+ return fi.completeBaseName ();
459
+ }
411
460
412
- auto appIdx = path.lastIndexOf (" /applications/" );
413
- if (appIdx != -1 ) {
414
- auto relativePath = path.mid (appIdx + 14 );
415
- auto relInfo = QFileInfo (relativePath);
461
+ const QStringList& DesktopEntryManager::desktopPaths () {
462
+ static const auto paths = []() {
463
+ auto dataPaths = QStringList ();
416
464
417
- auto dirPath = relInfo.path ();
418
- if (dirPath != " ." ) {
419
- id = dirPath.replace (' /' , ' -' ) + ' -' + relInfo.completeBaseName ();
465
+ auto dataHome = qEnvironmentVariable (" XDG_DATA_HOME" );
466
+ if (dataHome.isEmpty ()) {
467
+ if (qEnvironmentVariableIsSet (" HOME" )) {
468
+ dataHome = qEnvironmentVariable (" HOME" ) + " /.local/share" ;
469
+ }
470
+ }
471
+ if (!dataHome.isEmpty ()) {
472
+ dataPaths.append (dataHome + " /applications" );
473
+ }
474
+
475
+ auto dataDirs = qEnvironmentVariable (" XDG_DATA_DIRS" );
476
+ if (dataDirs.isEmpty ()) {
477
+ dataDirs = " /usr/local/share:/usr/share" ;
420
478
}
421
- }
422
479
423
- return id;
480
+ for (const auto & dir: dataDirs.split (' :' , Qt::SkipEmptyParts)) {
481
+ if (!dir.isEmpty ()) {
482
+ dataPaths.append (dir + " /applications" );
483
+ }
484
+ }
485
+
486
+ return dataPaths;
487
+ }();
488
+
489
+ return paths;
424
490
}
425
491
426
492
void DesktopEntryManager::onScanCompleted (const DesktopEntryScanResults& scanResults) {
@@ -430,30 +496,41 @@ void DesktopEntryManager::onScanCompleted(const DesktopEntryScanResults& scanRes
430
496
auto newEntries = QHash<QString, DesktopEntry*>();
431
497
auto newLowercaseEntries = QHash<QString, DesktopEntry*>();
432
498
433
- for (const auto & parsed: scanResults) {
434
- auto * dentry = new DesktopEntry (parsed.id , this );
435
- dentry->parseEntry (parsed.content );
499
+ for (const auto & data: scanResults) {
500
+ DesktopEntry* dentry = nullptr ;
501
+
502
+ if (auto it = oldEntries.find (data.id ); it != oldEntries.end ()) {
503
+ dentry = it.value ();
504
+ oldEntries.erase (it);
505
+ dentry->applyParsedData (data);
506
+ } else {
507
+ dentry = new DesktopEntry (data.id , this );
508
+ dentry->applyParsedData (data);
509
+ }
436
510
437
511
if (!dentry->isValid ()) {
438
- qCDebug (logDesktopEntry) << " Skipping desktop entry" << parsed.id ;
439
- delete dentry;
512
+ qCDebug (logDesktopEntry) << " Skipping desktop entry" << data.id ;
513
+ if (!oldEntries.contains (data.id )) {
514
+ dentry->deleteLater ();
515
+ }
440
516
continue ;
441
517
}
442
518
443
- qCDebug (logDesktopEntry) << " Found desktop entry" << parsed .id ;
519
+ qCDebug (logDesktopEntry) << " Found desktop entry" << data .id ;
444
520
445
- auto lowerId = parsed .id .toLower ();
521
+ auto lowerId = data .id .toLower ();
446
522
447
- auto conflictingId = newEntries.contains (parsed .id );
523
+ auto conflictingId = newEntries.contains (data .id );
448
524
449
525
if (conflictingId) {
450
- qCDebug (logDesktopEntry) << " Replacing old entry for" << parsed.id ;
451
- delete newEntries.value (parsed.id );
452
- newEntries.remove (parsed.id );
526
+ qCDebug (logDesktopEntry) << " Replacing old entry for" << data.id ;
527
+ if (auto * victim = newEntries.take (data.id )) {
528
+ victim->deleteLater ();
529
+ }
453
530
newLowercaseEntries.remove (lowerId);
454
531
}
455
532
456
- newEntries.insert (parsed .id , dentry);
533
+ newEntries.insert (data .id , dentry);
457
534
458
535
if (newLowercaseEntries.contains (lowerId)) {
459
536
qCInfo (logDesktopEntry).nospace ()
@@ -480,11 +557,8 @@ void DesktopEntryManager::onScanCompleted(const DesktopEntryScanResults& scanRes
480
557
this ->mApplications .diffUpdate (newApplications);
481
558
482
559
for (auto * e: oldEntries) {
483
- if (!this ->desktopEntries .contains (e->mId )) {
484
- e->deleteLater ();
485
- }
560
+ e->deleteLater ();
486
561
}
487
- oldEntries.clear ();
488
562
489
563
emit applicationsChanged ();
490
564
}
0 commit comments