1 /*
2 * applications-search-provider: Search provider for searching installed
3 * applications
4 *
5 * Copyright 2012-2020 Stephan Haller <nomad@froevel.de>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20 * MA 02110-1301, USA.
21 *
22 *
23 */
24
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28
29 #include <libxfdashboard/applications-search-provider.h>
30
31 #include <glib/gi18n-lib.h>
32 #include <gtk/gtk.h>
33 #include <errno.h>
34
35 #include <libxfdashboard/application-database.h>
36 #include <libxfdashboard/application-button.h>
37 #include <libxfdashboard/application.h>
38 #include <libxfdashboard/drag-action.h>
39 #include <libxfdashboard/click-action.h>
40 #include <libxfdashboard/popup-menu.h>
41 #include <libxfdashboard/popup-menu-item-button.h>
42 #include <libxfdashboard/popup-menu-item-separator.h>
43 #include <libxfdashboard/application-tracker.h>
44 #include <libxfdashboard/utils.h>
45 #include <libxfdashboard/enums.h>
46 #include <libxfdashboard/compat.h>
47 #include <libxfdashboard/debug.h>
48
49
50 /* Define this class in GObject system */
51 struct _XfdashboardApplicationsSearchProviderPrivate
52 {
53 /* Properties related */
54 XfdashboardApplicationsSearchProviderSortMode nextSortMode;
55
56 /* Instance related */
57 XfdashboardApplicationDatabase *appDB;
58 guint applicationAddedID;
59 guint applicationRemovedID;
60
61 GList *allApps;
62
63 XfconfChannel *xfconfChannel;
64 guint xfconfSortModeBindingID;
65 XfdashboardApplicationsSearchProviderSortMode currentSortMode;
66 };
67
68 G_DEFINE_TYPE_WITH_PRIVATE(XfdashboardApplicationsSearchProvider,
69 xfdashboard_applications_search_provider,
70 XFDASHBOARD_TYPE_SEARCH_PROVIDER)
71
72 /* Properties */
73 enum
74 {
75 PROP_0,
76
77 PROP_SORT_MODE,
78
79 PROP_LAST
80 };
81
82 static GParamSpec* XfdashboardApplicationsSearchProviderProperties[PROP_LAST]={ 0, };
83
84 /* IMPLEMENTATION: Private variables and methods */
85 #define SORT_MODE_XFCONF_PROP "/components/applications-search-provider/sort-mode"
86
87 #define DEFAULT_DELIMITERS "\t\n\r "
88
89 #define XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_FILE "applications-search-provider-statistics.ini"
90 #define XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_ENTRIES_GROUP "Entries"
91 #define XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_ENTRIES_COUNT "Count"
92 #define XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_USED_COUNTER_GROUP "Used Counters"
93
94 typedef struct _XfdashboardApplicationsSearchProviderGlobal XfdashboardApplicationsSearchProviderGlobal;
95 struct _XfdashboardApplicationsSearchProviderGlobal
96 {
97 gchar *filename;
98
99 GHashTable *stats;
100
101 guint shutdownSignalID;
102 guint applicationLaunchedSignalID;
103
104 guint maxUsedCounter;
105 };
106
107 G_LOCK_DEFINE_STATIC(_xfdashboard_applications_search_provider_statistics_lock);
108 XfdashboardApplicationsSearchProviderGlobal _xfdashboard_applications_search_provider_statistics={0, };
109
110 typedef struct _XfdashboardApplicationsSearchProviderStatistics XfdashboardApplicationsSearchProviderStatistics;
111 struct _XfdashboardApplicationsSearchProviderStatistics
112 {
113 gint refCount;
114
115 guint usedCounter;
116 };
117
118 /* Create, destroy, ref and unref statistics data */
_xfdashboard_applications_search_provider_statistics_new(void)119 static XfdashboardApplicationsSearchProviderStatistics* _xfdashboard_applications_search_provider_statistics_new(void)
120 {
121 XfdashboardApplicationsSearchProviderStatistics *data;
122
123 /* Create statistics data */
124 data=g_new0(XfdashboardApplicationsSearchProviderStatistics, 1);
125 if(!data) return(NULL);
126
127 /* Set up statistics data */
128 data->refCount=1;
129
130 return(data);
131 }
132
_xfdashboard_applications_search_provider_statistics_free(XfdashboardApplicationsSearchProviderStatistics * inData)133 static void _xfdashboard_applications_search_provider_statistics_free(XfdashboardApplicationsSearchProviderStatistics *inData)
134 {
135 g_return_if_fail(inData);
136
137 /* Release common allocated resources */
138 g_free(inData);
139 }
140
_xfdashboard_applications_search_provider_statistics_ref(XfdashboardApplicationsSearchProviderStatistics * inData)141 static XfdashboardApplicationsSearchProviderStatistics* _xfdashboard_applications_search_provider_statistics_ref(XfdashboardApplicationsSearchProviderStatistics *inData)
142 {
143 g_return_val_if_fail(inData, NULL);
144
145 inData->refCount++;
146 return(inData);
147 }
148
_xfdashboard_applications_search_provider_statistics_unref(XfdashboardApplicationsSearchProviderStatistics * inData)149 static void _xfdashboard_applications_search_provider_statistics_unref(XfdashboardApplicationsSearchProviderStatistics *inData)
150 {
151 g_return_if_fail(inData);
152
153 inData->refCount--;
154 if(inData->refCount==0) _xfdashboard_applications_search_provider_statistics_free(inData);
155 }
156
_xfdashboard_applications_search_provider_statistics_get(const gchar * inAppID)157 static XfdashboardApplicationsSearchProviderStatistics* _xfdashboard_applications_search_provider_statistics_get(const gchar *inAppID)
158 {
159 XfdashboardApplicationsSearchProviderStatistics *stats;
160
161 g_return_val_if_fail(_xfdashboard_applications_search_provider_statistics.stats, NULL);
162 g_return_val_if_fail(inAppID && *inAppID, NULL);
163
164 /* Lookup statistics data by application ID. If this application could not be found,
165 * then return NULL pointer.
166 */
167 if(!g_hash_table_lookup_extended(_xfdashboard_applications_search_provider_statistics.stats, inAppID, NULL, (gpointer*)&stats))
168 {
169 stats=NULL;
170 }
171
172 /* Return statistics data or NULL */
173 return(stats);
174 }
175
176 /* An application was launched successfully */
_xfdashboard_applications_search_provider_on_application_launched(XfdashboardApplication * inApplication,GAppInfo * inAppInfo,gpointer inUserData)177 static void _xfdashboard_applications_search_provider_on_application_launched(XfdashboardApplication *inApplication,
178 GAppInfo *inAppInfo,
179 gpointer inUserData)
180 {
181 const gchar *appID;
182 XfdashboardApplicationsSearchProviderStatistics *stats;
183
184 g_return_if_fail(G_IS_APP_INFO(inAppInfo));
185
186 /* Lock for thread-safety */
187 G_LOCK(_xfdashboard_applications_search_provider_statistics_lock);
188
189 /* Get application ID which is used to lookup and store statistics */
190 appID=g_app_info_get_id(inAppInfo);
191
192 /* Create new statistics data if application is new, otherwise take an extra
193 * reference on statistics data to keep it alive as it will be removed and
194 * re-added when updating and the removal may decrease the reference counter
195 * to zero which destroys the statistics data.
196 */
197 stats=_xfdashboard_applications_search_provider_statistics_get(appID);
198 if(!stats) stats=_xfdashboard_applications_search_provider_statistics_new();
199 else _xfdashboard_applications_search_provider_statistics_ref(stats);
200
201 /* Increase launch counter and remember it has highest launch counter if it
202 * is now higher than the one we remembered.
203 */
204 stats->usedCounter++;
205 if(stats->usedCounter>_xfdashboard_applications_search_provider_statistics.maxUsedCounter)
206 {
207 _xfdashboard_applications_search_provider_statistics.maxUsedCounter=stats->usedCounter;
208 }
209
210 /* Store updated statistics */
211 g_hash_table_insert(_xfdashboard_applications_search_provider_statistics.stats,
212 g_strdup(appID),
213 _xfdashboard_applications_search_provider_statistics_ref(stats));
214
215 /* Release extra reference we took to keep this statistics data alive */
216 _xfdashboard_applications_search_provider_statistics_unref(stats);
217
218 /* Unlock for thread-safety */
219 G_UNLOCK(_xfdashboard_applications_search_provider_statistics_lock);
220 }
221
222 /* Save statistics to file */
_xfdashboard_applications_search_provider_save_statistics(GError ** outError)223 static gboolean _xfdashboard_applications_search_provider_save_statistics(GError **outError)
224 {
225 GKeyFile *keyFile;
226 gchar *keyFileData;
227 gsize keyFileLength;
228 GList *allAppIDs;
229 GList *iter;
230 guint entriesCount;
231 gchar *fileFolder;
232 GError *error;
233
234 g_return_val_if_fail(outError==NULL || *outError==NULL, FALSE);
235
236 error=NULL;
237
238 /* If we have no filename do not store statistics but do not return error */
239 if(!_xfdashboard_applications_search_provider_statistics.filename) return(TRUE);
240
241 /* Create parent folders for key file if not available */
242 fileFolder=g_path_get_dirname(_xfdashboard_applications_search_provider_statistics.filename);
243 if(g_mkdir_with_parents(fileFolder, 0700)<0)
244 {
245 int errno_save;
246
247 /* Get error code */
248 errno_save=errno;
249
250 /* Set error */
251 g_set_error(outError,
252 G_IO_ERROR,
253 g_io_error_from_errno(errno_save),
254 "Could not create configuration folder for applications search provider at %s: %s",
255 fileFolder,
256 g_strerror(errno_save));
257
258 /* Release allocated resources */
259 if(fileFolder) g_free(fileFolder);
260
261 return(FALSE);
262 }
263
264 /* Create and set up key file to store statistics */
265 keyFile=g_key_file_new();
266
267 /* Get list of all applications from statistics hash table, iterate through
268 * all applications and store them in key file.
269 */
270 allAppIDs=g_hash_table_get_keys(_xfdashboard_applications_search_provider_statistics.stats);
271
272 g_key_file_set_integer(keyFile,
273 XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_ENTRIES_GROUP,
274 XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_ENTRIES_COUNT,
275 g_list_length(allAppIDs));
276
277 entriesCount=0;
278 for(iter=allAppIDs; iter; iter=g_list_next(iter))
279 {
280 gchar *name;
281 const gchar *appID;
282 XfdashboardApplicationsSearchProviderStatistics *stats;
283
284 /* Get current iterated application desktop ID */
285 appID=(const gchar*)iter->data;
286
287 /* Get statistics data (it must exist because we got a list of all keys) */
288 stats=_xfdashboard_applications_search_provider_statistics_get(appID);
289 g_assert(stats!=NULL);
290
291 /* Store application desktop ID in "entries" overview group and increase counter */
292 entriesCount++;
293
294 name=g_strdup_printf("%d", entriesCount);
295 g_key_file_set_string(keyFile,
296 XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_ENTRIES_GROUP,
297 name,
298 appID);
299 g_free(name);
300
301 /* Store statistics in key file in their groups but try to avoid to store
302 * default values to keep key file small.
303 */
304 if(stats->usedCounter>0)
305 {
306 g_key_file_set_integer(keyFile,
307 XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_USED_COUNTER_GROUP,
308 appID,
309 stats->usedCounter);
310 }
311 }
312
313 /* Store key file for statistics in file */
314 keyFileData=g_key_file_to_data(keyFile, &keyFileLength, NULL);
315 if(!g_file_set_contents(_xfdashboard_applications_search_provider_statistics.filename, keyFileData, keyFileLength, &error))
316 {
317 /* Propagate error */
318 g_propagate_error(outError, error);
319
320 /* Release allocated resources */
321 if(fileFolder) g_free(fileFolder);
322 if(keyFileData) g_free(keyFileData);
323 if(keyFile) g_key_file_free(keyFile);
324
325 return(FALSE);
326 }
327
328 /* Release allocated resources */
329 if(fileFolder) g_free(fileFolder);
330 if(keyFileData) g_free(keyFileData);
331 if(keyFile) g_key_file_free(keyFile);
332
333 /* If we get here saving statistics file was successful */
334 return(TRUE);
335 }
336
337 /* Load statistics from file */
_xfdashboard_applications_search_provider_load_statistics(XfdashboardApplicationsSearchProvider * self,GError ** outError)338 static gboolean _xfdashboard_applications_search_provider_load_statistics(XfdashboardApplicationsSearchProvider *self,
339 GError **outError)
340 {
341 GKeyFile *keyFile;
342 GList *allAppIDs;
343 GList *iter;
344 guint entriesCount;
345 GError *error;
346
347 g_return_val_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(self), FALSE);
348 g_return_val_if_fail(outError==NULL || *outError==NULL, FALSE);
349
350 error=NULL;
351
352 /* If no statistics were set up, we cannot load from file */
353 if(!_xfdashboard_applications_search_provider_statistics.stats)
354 {
355 /* Set error */
356 g_set_error(outError,
357 G_IO_ERROR,
358 G_IO_ERROR_FAILED,
359 "Statistics were not initialized");
360
361 return(FALSE);
362 }
363
364 /* Get path to statistics file to load statistics from */
365 if(!_xfdashboard_applications_search_provider_statistics.filename)
366 {
367 _xfdashboard_applications_search_provider_statistics.filename=
368 g_build_filename(g_get_user_data_dir(),
369 "xfdashboard",
370 XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_FILE,
371 NULL);
372
373 if(!_xfdashboard_applications_search_provider_statistics.filename)
374 {
375 /* Set error */
376 g_set_error(outError,
377 G_IO_ERROR,
378 G_IO_ERROR_NOT_FOUND,
379 "Could not build path to statistics file of applications search provider");
380
381 return(FALSE);
382 }
383 }
384 XFDASHBOARD_DEBUG(self, APPLICATIONS,
385 "Will load statistics of applications search provider from %s",
386 _xfdashboard_applications_search_provider_statistics.filename);
387
388 /* If statistics file does not exist then return immediately but with success */
389 if(!g_file_test(_xfdashboard_applications_search_provider_statistics.filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))
390 {
391 XFDASHBOARD_DEBUG(self, APPLICATIONS,
392 "Statistics file %s does not exists. Will create empty statistics database for applications search provider",
393 _xfdashboard_applications_search_provider_statistics.filename);
394
395 return(TRUE);
396 }
397
398 /* Load statistics from key file */
399 keyFile=g_key_file_new();
400 if(!g_key_file_load_from_file(keyFile, _xfdashboard_applications_search_provider_statistics.filename, G_KEY_FILE_NONE, &error))
401 {
402 /* Propagate error */
403 g_propagate_error(outError, error);
404
405 /* Release allocated resources */
406 if(keyFile) g_key_file_free(keyFile);
407
408 return(FALSE);
409 }
410
411 /* Get number of applications and their application desktop IDs from key file */
412 entriesCount=g_key_file_get_integer(keyFile,
413 XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_ENTRIES_GROUP,
414 XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_ENTRIES_COUNT,
415 &error);
416 if(error)
417 {
418 /* Propagate error */
419 g_propagate_error(outError, error);
420
421 /* Release allocated resources */
422 if(keyFile) g_key_file_free(keyFile);
423
424 return(FALSE);
425 }
426 XFDASHBOARD_DEBUG(self, APPLICATIONS,
427 "Will load statistics for %d applications",
428 entriesCount);
429
430 allAppIDs=NULL;
431 while(entriesCount>0)
432 {
433 gchar *name;
434 gchar *appID;
435
436 /* Get application desktop ID */
437 name=g_strdup_printf("%d", entriesCount);
438 appID=g_key_file_get_string(keyFile,
439 XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_ENTRIES_GROUP,
440 name,
441 &error);
442 g_free(name);
443
444 if(error)
445 {
446 /* Propagate error */
447 g_propagate_error(outError, error);
448
449 /* Release allocated resources */
450 if(appID) g_free(appID);
451 if(allAppIDs) g_list_free_full(allAppIDs, g_free);
452 if(keyFile) g_key_file_free(keyFile);
453
454 return(FALSE);
455 }
456
457 /* Store application desktop ID in list to iterate */
458 allAppIDs=g_list_prepend(allAppIDs, appID);
459 XFDASHBOARD_DEBUG(self, APPLICATIONS,
460 "Will load statistics for application '%s'",
461 appID);
462
463 /* Continue with next application in entries group */
464 entriesCount--;
465 }
466
467 /* Iterate through all application desktop IDs and create statistics data
468 * with default values for each one first. Then try to load stored values
469 * from key file.
470 */
471 for(iter=allAppIDs; iter; iter=g_list_next(iter))
472 {
473 const gchar *appID;
474 XfdashboardApplicationsSearchProviderStatistics *stats;
475
476 /* Get current iterated application desktop ID */
477 appID=(const gchar*)iter->data;
478
479 /* Create statistics data for application with default values */
480 stats=_xfdashboard_applications_search_provider_statistics_new();
481 if(!stats)
482 {
483 g_critical("Could not create statistics data for application '%s' of applications search provider", appID);
484 continue;
485 }
486
487 /* Try to load stored values for application from key file */
488 if(g_key_file_has_key(keyFile, XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_USED_COUNTER_GROUP, appID, NULL))
489 {
490 stats->usedCounter=g_key_file_get_integer(keyFile,
491 XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_USED_COUNTER_GROUP,
492 appID,
493 &error);
494 if(error)
495 {
496 g_critical("Could not get value from group [%s] for application %s from statistics file of applications search provider: %s",
497 XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_USED_COUNTER_GROUP,
498 appID,
499 error->message);
500 g_clear_error(&error);
501 }
502
503 if(stats->usedCounter>_xfdashboard_applications_search_provider_statistics.maxUsedCounter)
504 {
505 _xfdashboard_applications_search_provider_statistics.maxUsedCounter=stats->usedCounter;
506 }
507 }
508
509 /* Store statistics data for application in hash-table */
510 g_hash_table_insert(_xfdashboard_applications_search_provider_statistics.stats, g_strdup(appID), _xfdashboard_applications_search_provider_statistics_ref(stats));
511 XFDASHBOARD_DEBUG(self, APPLICATIONS,
512 "Loaded and stored statistics for '%s' for applications search provider",
513 appID);
514
515 /* Release allocated resources */
516 _xfdashboard_applications_search_provider_statistics_unref(stats);
517 }
518
519 /* Release allocated resources */
520 if(allAppIDs) g_list_free_full(allAppIDs, g_free);
521 if(keyFile) g_key_file_free(keyFile);
522
523 /* If we get here saving statistics file was successful */
524 XFDASHBOARD_DEBUG(self, APPLICATIONS,
525 "Loaded statistics of applications search provider from %s",
526 _xfdashboard_applications_search_provider_statistics.filename);
527
528 return(TRUE);
529 }
530
531 /* Destroy statistics for this search provider */
_xfdashboard_applications_search_provider_destroy_statistics(void)532 static void _xfdashboard_applications_search_provider_destroy_statistics(void)
533 {
534 XfdashboardApplication *application;
535 GError *error;
536
537 error=NULL;
538
539 /* Only existing statistics can be destroyed */
540 if(!_xfdashboard_applications_search_provider_statistics.stats) return;
541
542 /* Lock for thread-safety */
543 G_LOCK(_xfdashboard_applications_search_provider_statistics_lock);
544
545 /* Get application instance */
546 application=xfdashboard_application_get_default();
547
548 /* Disconnect application "shutdown" signal handler */
549 if(_xfdashboard_applications_search_provider_statistics.shutdownSignalID)
550 {
551 g_signal_handler_disconnect(application, _xfdashboard_applications_search_provider_statistics.shutdownSignalID);
552 _xfdashboard_applications_search_provider_statistics.shutdownSignalID=0;
553 }
554
555 /* Disconnect application "application-launched" signal handler */
556 if(_xfdashboard_applications_search_provider_statistics.applicationLaunchedSignalID)
557 {
558 g_signal_handler_disconnect(application, _xfdashboard_applications_search_provider_statistics.applicationLaunchedSignalID);
559 _xfdashboard_applications_search_provider_statistics.applicationLaunchedSignalID=0;
560 }
561
562 /* Save statistics to file */
563 if(!_xfdashboard_applications_search_provider_save_statistics(&error))
564 {
565 g_critical("Failed to save statistics of applications search provider to %s: %s",
566 _xfdashboard_applications_search_provider_statistics.filename,
567 error ? error->message : "Unknown error");
568 if(error) g_clear_error(&error);
569 }
570
571 /* Destroy statistics */
572 XFDASHBOARD_DEBUG(NULL, APPLICATIONS, "Destroying statistics of applications search provider");
573 g_hash_table_destroy(_xfdashboard_applications_search_provider_statistics.stats);
574 _xfdashboard_applications_search_provider_statistics.stats=NULL;
575
576 /* Destroy filename for statistics */
577 if(_xfdashboard_applications_search_provider_statistics.filename)
578 {
579 g_free(_xfdashboard_applications_search_provider_statistics.filename);
580 _xfdashboard_applications_search_provider_statistics.filename=NULL;
581 }
582
583 /* Reset other variables */
584 _xfdashboard_applications_search_provider_statistics.maxUsedCounter=0;
585
586 /* Unlock for thread-safety */
587 G_UNLOCK(_xfdashboard_applications_search_provider_statistics_lock);
588 }
589
590 /* Create and load statistics for this search provider if not done already */
_xfdashboard_applications_search_provider_create_statistics(XfdashboardApplicationsSearchProvider * self)591 static void _xfdashboard_applications_search_provider_create_statistics(XfdashboardApplicationsSearchProvider *self)
592 {
593 XfdashboardApplication *application;
594 GError *error;
595
596 g_return_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(self));
597
598 error=NULL;
599
600 /* Statistics were already set up */
601 if(_xfdashboard_applications_search_provider_statistics.stats) return;
602
603 g_assert(!_xfdashboard_applications_search_provider_statistics.shutdownSignalID);
604 g_assert(!_xfdashboard_applications_search_provider_statistics.applicationLaunchedSignalID);
605
606 /* Lock for thread-safety */
607 G_LOCK(_xfdashboard_applications_search_provider_statistics_lock);
608
609 /* Initialize non-critical variables */
610 _xfdashboard_applications_search_provider_statistics.maxUsedCounter=0;
611
612 /* Create hash-table for statistics */
613 _xfdashboard_applications_search_provider_statistics.stats=
614 g_hash_table_new_full(g_str_hash,
615 g_str_equal,
616 g_free,
617 (GDestroyNotify)_xfdashboard_applications_search_provider_statistics_unref);
618 XFDASHBOARD_DEBUG(self, APPLICATIONS, "Created statistics of applications search provider");
619
620 /* Load statistics from file */
621 if(!_xfdashboard_applications_search_provider_load_statistics(self, &error))
622 {
623 g_critical("Failed to load statistics of applications search provider from %s: %s",
624 _xfdashboard_applications_search_provider_statistics.filename,
625 error ? error->message : "Unknown error");
626 if(error) g_clear_error(&error);
627
628 /* Destroy hash-table to avoid the half-loaded hash-table being stored again
629 * and overriding existing statistics file (even if it may be broken).
630 * Also release
631 */
632 if(_xfdashboard_applications_search_provider_statistics.stats)
633 {
634 g_hash_table_destroy(_xfdashboard_applications_search_provider_statistics.stats);
635 _xfdashboard_applications_search_provider_statistics.stats=NULL;
636 }
637
638 if(_xfdashboard_applications_search_provider_statistics.filename)
639 {
640 g_free(_xfdashboard_applications_search_provider_statistics.filename);
641 _xfdashboard_applications_search_provider_statistics.filename=NULL;
642 }
643
644 /* Unlock for thread-safety */
645 G_UNLOCK(_xfdashboard_applications_search_provider_statistics_lock);
646
647 /* Return here to prevent further initializations of statistics
648 * which are not used and not deinitialized in 'destroy' function.
649 */
650 return;
651 }
652
653 /* Get application instance */
654 application=xfdashboard_application_get_default();
655
656 /* Connect to "shutdown" signal of application to clean up statistics */
657 _xfdashboard_applications_search_provider_statistics.shutdownSignalID=
658 g_signal_connect(application,
659 "shutdown-final",
660 G_CALLBACK(_xfdashboard_applications_search_provider_destroy_statistics),
661 NULL);
662
663 /* Connect to "application-launched" signal of application to track app launches */
664 _xfdashboard_applications_search_provider_statistics.applicationLaunchedSignalID=
665 g_signal_connect(application,
666 "application-launched",
667 G_CALLBACK(_xfdashboard_applications_search_provider_on_application_launched),
668 NULL);
669
670 /* Unlock for thread-safety */
671 G_UNLOCK(_xfdashboard_applications_search_provider_statistics_lock);
672 }
673
674 /* An application was added to database */
_xfdashboard_applications_search_provider_on_application_added(XfdashboardApplicationsSearchProvider * self,GAppInfo * inAppInfo,gpointer inUserData)675 static void _xfdashboard_applications_search_provider_on_application_added(XfdashboardApplicationsSearchProvider *self,
676 GAppInfo *inAppInfo,
677 gpointer inUserData)
678 {
679 XfdashboardApplicationsSearchProviderPrivate *priv;
680
681 g_return_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(self));
682
683 priv=self->priv;
684
685 /* Release current list of all installed applications */
686 if(priv->allApps)
687 {
688 g_list_free_full(priv->allApps, g_object_unref);
689 priv->allApps=NULL;
690 }
691
692 /* Get new list of all installed applications */
693 priv->allApps=xfdashboard_application_database_get_all_applications(priv->appDB);
694 }
695
696 /* An application was removed to database */
_xfdashboard_applications_search_provider_on_application_removed(XfdashboardApplicationsSearchProvider * self,GAppInfo * inAppInfo,gpointer inUserData)697 static void _xfdashboard_applications_search_provider_on_application_removed(XfdashboardApplicationsSearchProvider *self,
698 GAppInfo *inAppInfo,
699 gpointer inUserData)
700 {
701 XfdashboardApplicationsSearchProviderPrivate *priv;
702
703 g_return_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(self));
704
705 priv=self->priv;
706
707 /* Release current list of all installed applications */
708 if(priv->allApps)
709 {
710 g_list_free_full(priv->allApps, g_object_unref);
711 priv->allApps=NULL;
712 }
713
714 /* Get new list of all installed applications */
715 priv->allApps=xfdashboard_application_database_get_all_applications(priv->appDB);
716 }
717
718 /* User selected to open a new window or to launch that application at pop-up menu */
_xfdashboard_applications_search_provider_on_popup_menu_item_launch(XfdashboardPopupMenuItem * inMenuItem,gpointer inUserData)719 static void _xfdashboard_applications_search_provider_on_popup_menu_item_launch(XfdashboardPopupMenuItem *inMenuItem,
720 gpointer inUserData)
721 {
722 GAppInfo *appInfo;
723 XfdashboardApplicationTracker *appTracker;
724 GIcon *gicon;
725 const gchar *iconName;
726
727 g_return_if_fail(XFDASHBOARD_IS_POPUP_MENU_ITEM(inMenuItem));
728 g_return_if_fail(G_IS_APP_INFO(inUserData));
729
730 appInfo=G_APP_INFO(inUserData);
731 iconName=NULL;
732
733 /* Get icon of application */
734 gicon=g_app_info_get_icon(appInfo);
735 if(gicon) iconName=g_icon_to_string(gicon);
736
737 /* Check if we should launch that application or to open a new window */
738 appTracker=xfdashboard_application_tracker_get_default();
739 if(!xfdashboard_application_tracker_is_running_by_app_info(appTracker, appInfo))
740 {
741 GAppLaunchContext *context;
742 GError *error;
743
744 /* Create context to start application at */
745 context=xfdashboard_create_app_context(NULL);
746
747 /* Try to launch application */
748 error=NULL;
749 if(!g_app_info_launch(appInfo, NULL, context, &error))
750 {
751 /* Show notification about failed application launch */
752 xfdashboard_notify(CLUTTER_ACTOR(inMenuItem),
753 iconName,
754 _("Launching application '%s' failed: %s"),
755 g_app_info_get_display_name(appInfo),
756 (error && error->message) ? error->message : _("unknown error"));
757 g_warning("Launching application '%s' failed: %s",
758 g_app_info_get_display_name(appInfo),
759 (error && error->message) ? error->message : "unknown error");
760 if(error) g_error_free(error);
761 }
762 else
763 {
764 /* Show notification about successful application launch */
765 xfdashboard_notify(CLUTTER_ACTOR(inMenuItem),
766 iconName,
767 _("Application '%s' launched"),
768 g_app_info_get_display_name(appInfo));
769
770 /* Emit signal for successful application launch */
771 g_signal_emit_by_name(xfdashboard_application_get_default(), "application-launched", appInfo);
772
773 /* Quit application */
774 xfdashboard_application_suspend_or_quit(NULL);
775 }
776
777 /* Release allocated resources */
778 g_object_unref(context);
779 }
780
781 /* Release allocated resources */
782 g_object_unref(appTracker);
783 g_object_unref(gicon);
784 }
785
786 /* A right-click might have happened on an application icon */
_xfdashboard_applications_search_provider_on_popup_menu(XfdashboardApplicationsSearchProvider * self,ClutterActor * inActor,gpointer inUserData)787 static void _xfdashboard_applications_search_provider_on_popup_menu(XfdashboardApplicationsSearchProvider *self,
788 ClutterActor *inActor,
789 gpointer inUserData)
790 {
791 XfdashboardApplicationButton *button;
792 XfdashboardClickAction *action;
793
794 g_return_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(self));
795 g_return_if_fail(XFDASHBOARD_IS_APPLICATION_BUTTON(inActor));
796 g_return_if_fail(XFDASHBOARD_IS_CLICK_ACTION(inUserData));
797
798 button=XFDASHBOARD_APPLICATION_BUTTON(inActor);
799 action=XFDASHBOARD_CLICK_ACTION(inUserData);
800
801 /* Check if right button was used when the application button was clicked */
802 if(xfdashboard_click_action_get_button(action)==XFDASHBOARD_CLICK_ACTION_RIGHT_BUTTON)
803 {
804 ClutterActor *popup;
805 ClutterActor *menuItem;
806 GAppInfo *appInfo;
807 XfdashboardApplicationTracker *appTracker;
808 gchar *sourceStyleClass;
809
810 /* Get app info for application button as it is needed most the time */
811 appInfo=xfdashboard_application_button_get_app_info(button);
812 if(!appInfo)
813 {
814 g_critical("No application information available for clicked application button.");
815 return;
816 }
817
818 /* Create pop-up menu */
819 popup=xfdashboard_popup_menu_new();
820 xfdashboard_popup_menu_set_destroy_on_cancel(XFDASHBOARD_POPUP_MENU(popup), TRUE);
821 xfdashboard_popup_menu_set_title(XFDASHBOARD_POPUP_MENU(popup), g_app_info_get_display_name(appInfo));
822 xfdashboard_popup_menu_set_title_gicon(XFDASHBOARD_POPUP_MENU(popup), g_app_info_get_icon(appInfo));
823
824 /* Add each open window to pop-up of application */
825 if(xfdashboard_application_button_add_popup_menu_items_for_windows(button, XFDASHBOARD_POPUP_MENU(popup))>0)
826 {
827 /* Add a separator to split windows from other actions in pop-up menu */
828 menuItem=xfdashboard_popup_menu_item_separator_new();
829 clutter_actor_set_x_expand(menuItem, TRUE);
830 xfdashboard_popup_menu_add_item(XFDASHBOARD_POPUP_MENU(popup), XFDASHBOARD_POPUP_MENU_ITEM(menuItem));
831 }
832
833 /* Add menu item to launch application if it is not running */
834 appTracker=xfdashboard_application_tracker_get_default();
835 if(!xfdashboard_application_tracker_is_running_by_app_info(appTracker, appInfo))
836 {
837 menuItem=xfdashboard_popup_menu_item_button_new();
838 xfdashboard_label_set_text(XFDASHBOARD_LABEL(menuItem), _("Launch"));
839 clutter_actor_set_x_expand(menuItem, TRUE);
840 xfdashboard_popup_menu_add_item(XFDASHBOARD_POPUP_MENU(popup), XFDASHBOARD_POPUP_MENU_ITEM(menuItem));
841
842 g_signal_connect(menuItem,
843 "activated",
844 G_CALLBACK(_xfdashboard_applications_search_provider_on_popup_menu_item_launch),
845 appInfo);
846 }
847 g_object_unref(appTracker);
848
849 /* Add application actions */
850 xfdashboard_application_button_add_popup_menu_items_for_actions(button, XFDASHBOARD_POPUP_MENU(popup));
851
852 /* Set style class as pop-up menu has no source set to create style
853 * class automatically because this class is not derived from an actor
854 * class.
855 */
856 sourceStyleClass=g_strdup_printf("popup-menu-source-%s", G_OBJECT_TYPE_NAME(self));
857 xfdashboard_stylable_add_class(XFDASHBOARD_STYLABLE(popup), sourceStyleClass);
858 g_free(sourceStyleClass);
859
860 /* Activate pop-up menu */
861 xfdashboard_popup_menu_activate(XFDASHBOARD_POPUP_MENU(popup));
862 }
863 }
864
865 /* Drag of an menu item begins */
_xfdashboard_applications_search_provider_on_drag_begin(ClutterDragAction * inAction,ClutterActor * inActor,gfloat inStageX,gfloat inStageY,ClutterModifierType inModifiers,gpointer inUserData)866 static void _xfdashboard_applications_search_provider_on_drag_begin(ClutterDragAction *inAction,
867 ClutterActor *inActor,
868 gfloat inStageX,
869 gfloat inStageY,
870 ClutterModifierType inModifiers,
871 gpointer inUserData)
872 {
873 GAppInfo *appInfo;
874 ClutterActor *dragHandle;
875 ClutterStage *stage;
876
877 g_return_if_fail(CLUTTER_IS_DRAG_ACTION(inAction));
878 g_return_if_fail(XFDASHBOARD_IS_APPLICATION_BUTTON(inActor));
879 g_return_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(inUserData));
880
881 /* Get stage */
882 stage=CLUTTER_STAGE(clutter_actor_get_stage(inActor));
883
884 /* Create a application icon for drag handle */
885 appInfo=xfdashboard_application_button_get_app_info(XFDASHBOARD_APPLICATION_BUTTON(inActor));
886
887 dragHandle=xfdashboard_application_button_new_from_app_info(appInfo);
888 clutter_actor_set_position(dragHandle, inStageX, inStageY);
889 clutter_actor_add_child(CLUTTER_ACTOR(stage), dragHandle);
890
891 clutter_drag_action_set_drag_handle(inAction, dragHandle);
892 }
893
894 /* Drag of a result item ends */
_xfdashboard_applications_search_provider_on_drag_end(ClutterDragAction * inAction,ClutterActor * inActor,gfloat inStageX,gfloat inStageY,ClutterModifierType inModifiers,gpointer inUserData)895 static void _xfdashboard_applications_search_provider_on_drag_end(ClutterDragAction *inAction,
896 ClutterActor *inActor,
897 gfloat inStageX,
898 gfloat inStageY,
899 ClutterModifierType inModifiers,
900 gpointer inUserData)
901 {
902 ClutterActor *dragHandle;
903
904 g_return_if_fail(CLUTTER_IS_DRAG_ACTION(inAction));
905 g_return_if_fail(XFDASHBOARD_IS_APPLICATION_BUTTON(inActor));
906 g_return_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(inUserData));
907
908 /* Destroy clone of application icon used as drag handle */
909 dragHandle=clutter_drag_action_get_drag_handle(inAction);
910 if(dragHandle)
911 {
912 #if CLUTTER_CHECK_VERSION(1, 14, 0)
913 /* Only unset drag handle if not running Clutter in version
914 * 1.12. This prevents a critical warning message in 1.12.
915 * Later versions of Clutter are fixed already.
916 */
917 clutter_drag_action_set_drag_handle(inAction, NULL);
918 #endif
919 xfdashboard_actor_destroy(dragHandle);
920 }
921 }
922
923 /* Check if given app info matches search terms and return score as fraction
924 * between 0.0and 1.0 - so called "relevance". A negative score means that
925 * the given app info does not match at all.
926 */
_xfdashboard_applications_search_provider_score(XfdashboardApplicationsSearchProvider * self,gchar ** inSearchTerms,GAppInfo * inAppInfo)927 static gfloat _xfdashboard_applications_search_provider_score(XfdashboardApplicationsSearchProvider *self,
928 gchar **inSearchTerms,
929 GAppInfo *inAppInfo)
930 {
931 XfdashboardApplicationsSearchProviderPrivate *priv;
932 gchar *title;
933 gchar *description;
934 GList *keywords;
935 const gchar *command;
936 const gchar *value;
937 gint matchesFound, matchesExpected;
938 gfloat pointsSearch;
939 gfloat score;
940
941 g_return_val_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(self), -1.0f);
942 g_return_val_if_fail(G_IS_APP_INFO(inAppInfo), -1.0f);
943
944 priv=self->priv;
945 score=-1.0f;
946 keywords=NULL;
947
948 /* Empty search term matches no menu item */
949 if(!inSearchTerms) return(0.0f);
950
951 matchesExpected=g_strv_length(inSearchTerms);
952 if(matchesExpected==0) return(0.0f);
953
954 /* Calculate the highest score points possible which is the highest
955 * launch count among all applications, display name matches all search terms,
956 * description matches all search terms and also the command matches all
957 * search terms.
958 *
959 * But a matching display names weights more than a matching description and
960 * also a matching command or keyword weights more than a matching description.
961 * So the weights are 0.4 for matching display names, 0.25 for matching commands,
962 * 0.25 for matching keywords and 0.1 for matching descriptions.
963 *
964 * While iterating through all search terms we add the weights "points" for
965 * each matching item and when we iterated through all search terms we divide
966 * the total weight "points" by the number of search terms to get the average
967 * which is also the result score when *not* taking the launch count of
968 * application into account.
969 */
970 value=g_app_info_get_display_name(inAppInfo);
971 if(value) title=g_utf8_strdown(value, -1);
972 else title=NULL;
973
974 value=g_app_info_get_description(inAppInfo);
975 if(value) description=g_utf8_strdown(value, -1);
976 else description=NULL;
977
978 command=g_app_info_get_executable(inAppInfo);
979
980 if(XFDASHBOARD_IS_DESKTOP_APP_INFO(inAppInfo))
981 {
982 GList *appKeywords;
983 GList *iter;
984
985 appKeywords=xfdashboard_desktop_app_info_get_keywords(XFDASHBOARD_DESKTOP_APP_INFO(inAppInfo));
986 for(iter=appKeywords; iter; iter=g_list_next(iter))
987 {
988 keywords=g_list_prepend(keywords, g_utf8_strdown(iter->data, -1));
989 }
990 }
991
992 matchesFound=0;
993 pointsSearch=0.0f;
994 while(*inSearchTerms)
995 {
996 gboolean termMatch;
997 gchar *commandPos;
998 gfloat pointsTerm;
999
1000 /* Reset "found" indicator and score of current search term */
1001 termMatch=FALSE;
1002 pointsTerm=0.0f;
1003
1004 /* Check for current search term */
1005 if(title &&
1006 g_strstr_len(title, -1, *inSearchTerms))
1007 {
1008 pointsTerm+=0.4;
1009 termMatch=TRUE;
1010 }
1011
1012 if(keywords)
1013 {
1014 GList *iter;
1015
1016 for(iter=keywords; iter; iter=g_list_next(iter))
1017 {
1018 if(iter->data &&
1019 g_strstr_len(iter->data, -1, *inSearchTerms))
1020 {
1021 pointsTerm+=0.25;
1022 termMatch=TRUE;
1023 break;
1024 }
1025 }
1026 }
1027
1028 if(command)
1029 {
1030 commandPos=g_strstr_len(command, -1, *inSearchTerms);
1031 if(commandPos &&
1032 (commandPos==command || *(commandPos-1)==G_DIR_SEPARATOR))
1033 {
1034 pointsTerm+=0.25;
1035 termMatch=TRUE;
1036 }
1037 }
1038
1039 if(description &&
1040 g_strstr_len(description, -1, *inSearchTerms))
1041 {
1042 pointsTerm+=0.1;
1043 termMatch=TRUE;
1044 }
1045
1046 /* Increase match counter if we found a match */
1047 if(termMatch)
1048 {
1049 matchesFound++;
1050 pointsSearch+=pointsTerm;
1051 }
1052
1053 /* Continue with next search term */
1054 inSearchTerms++;
1055 }
1056
1057 /* If we got a match in either title, description or command for each search term
1058 * then calculate score and also check if we should take the number of
1059 * launches of this application into account.
1060 */
1061 if(matchesFound>=matchesExpected)
1062 {
1063 gfloat currentPoints;
1064 gfloat maxPoints;
1065 XfdashboardApplicationsSearchProviderStatistics *stats;
1066
1067 currentPoints=0.0f;
1068 maxPoints=0.0f;
1069
1070 /* Set maximum points to the number of expected number of matches
1071 * if we should take title, description and command into calculation.
1072 */
1073 if(priv->currentSortMode & XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_SORT_MODE_NAMES)
1074 {
1075 currentPoints+=pointsSearch;
1076 maxPoints+=matchesExpected*1.0f;
1077 }
1078
1079 /* If used counter should be taken into calculation add the highest number
1080 * of any application to the highest points possible and also add the number
1081 * of launches of this application to the total points we got so far.
1082 */
1083 if(priv->currentSortMode & XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_SORT_MODE_MOST_USED)
1084 {
1085 maxPoints+=(_xfdashboard_applications_search_provider_statistics.maxUsedCounter*1.0f);
1086
1087 stats=_xfdashboard_applications_search_provider_statistics_get(g_app_info_get_id(inAppInfo));
1088 if(stats) currentPoints+=(stats->usedCounter*1.0f);
1089 }
1090
1091 /* Calculate score but if maximum points is still zero we should do a simple
1092 * match by setting score to 1.
1093 */
1094 if(maxPoints>0.0f) score=currentPoints/maxPoints;
1095 else score=1.0f;
1096 }
1097
1098 /* Release allocated resources */
1099 if(keywords) g_list_free_full(keywords, g_free);
1100 if(description) g_free(description);
1101 if(title) g_free(title);
1102
1103 /* Return score of this application for requested search terms */
1104 return(score);
1105 }
1106
1107 /* Callback to sort each item in result set */
_xfdashboard_applications_search_provider_sort_result_set(GVariant * inLeft,GVariant * inRight,gpointer inUserData)1108 static gint _xfdashboard_applications_search_provider_sort_result_set(GVariant *inLeft,
1109 GVariant *inRight,
1110 gpointer inUserData)
1111 {
1112 XfdashboardApplicationsSearchProvider *self;
1113 XfdashboardApplicationsSearchProviderPrivate *priv;
1114 const gchar *leftID;
1115 const gchar *rightID;
1116 GAppInfo *leftAppInfo;
1117 GAppInfo *rightAppInfo;
1118 const gchar *leftName;
1119 const gchar *rightName;
1120 gchar *lowerLeftName;
1121 gchar *lowerRightName;
1122 gint result;
1123
1124 g_return_val_if_fail(inLeft, 0);
1125 g_return_val_if_fail(inRight, 0);
1126 g_return_val_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(inUserData), 0);
1127
1128 self=XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER(inUserData);
1129 priv=self->priv;
1130
1131 /* Get desktop IDs of both items */
1132 leftID=g_variant_get_string(inLeft, NULL);
1133 rightID=g_variant_get_string(inRight, NULL);
1134
1135 /* Get desktop application information of both items */
1136 leftAppInfo=xfdashboard_application_database_lookup_desktop_id(priv->appDB, leftID);
1137 if(leftAppInfo) leftName=g_app_info_get_display_name(G_APP_INFO(leftAppInfo));
1138 else leftName=NULL;
1139
1140 rightAppInfo=xfdashboard_application_database_lookup_desktop_id(priv->appDB, rightID);
1141 if(rightAppInfo) rightName=g_app_info_get_display_name(G_APP_INFO(rightAppInfo));
1142 else rightName=NULL;
1143
1144 /* Get result of comparing both desktop application information objects */
1145 if(leftName) lowerLeftName=g_utf8_strdown(leftName, -1);
1146 else lowerLeftName=NULL;
1147
1148 if(rightName) lowerRightName=g_utf8_strdown(rightName, -1);
1149 else lowerRightName=NULL;
1150
1151 result=g_strcmp0(lowerLeftName, lowerRightName);
1152
1153 /* Release allocated resources */
1154 if(rightAppInfo) g_object_unref(rightAppInfo);
1155 if(leftAppInfo) g_object_unref(leftAppInfo);
1156 if(lowerLeftName) g_free(lowerLeftName);
1157 if(lowerRightName) g_free(lowerRightName);
1158
1159 /* Return result */
1160 return(result);
1161 }
1162
1163 /* IMPLEMENTATION: XfdashboardSearchProvider */
_xfdashboard_applications_search_provider_initialize(XfdashboardSearchProvider * inProvider)1164 static void _xfdashboard_applications_search_provider_initialize(XfdashboardSearchProvider *inProvider)
1165 {
1166 g_return_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(inProvider));
1167
1168 /* Create and load statistics hash-table (will only be done once) */
1169 _xfdashboard_applications_search_provider_create_statistics(XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER(inProvider));
1170 }
1171
1172 /* Get display name for this search provider */
_xfdashboard_applications_search_provider_get_name(XfdashboardSearchProvider * inProvider)1173 static const gchar* _xfdashboard_applications_search_provider_get_name(XfdashboardSearchProvider *inProvider)
1174 {
1175 return(_("Applications"));
1176 }
1177
1178 /* Get icon-name for this search provider */
_xfdashboard_applications_search_provider_get_icon(XfdashboardSearchProvider * inProvider)1179 static const gchar* _xfdashboard_applications_search_provider_get_icon(XfdashboardSearchProvider *inProvider)
1180 {
1181 return("go-home");
1182 }
1183
1184 /* Get result set for requested search terms */
_xfdashboard_applications_search_provider_get_result_set(XfdashboardSearchProvider * inProvider,const gchar ** inSearchTerms,XfdashboardSearchResultSet * inPreviousResultSet)1185 static XfdashboardSearchResultSet* _xfdashboard_applications_search_provider_get_result_set(XfdashboardSearchProvider *inProvider,
1186 const gchar **inSearchTerms,
1187 XfdashboardSearchResultSet *inPreviousResultSet)
1188 {
1189 XfdashboardApplicationsSearchProvider *self;
1190 XfdashboardApplicationsSearchProviderPrivate *priv;
1191 XfdashboardSearchResultSet *resultSet;
1192 GList *iter;
1193 guint numberTerms;
1194 gchar **terms, **termsIter;
1195 XfdashboardDesktopAppInfo *appInfo;
1196 gfloat score;
1197
1198 g_return_val_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(inProvider), NULL);
1199
1200 self=XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER(inProvider);
1201 priv=self->priv;
1202
1203 /* Set new match mode */
1204 priv->currentSortMode=priv->nextSortMode;
1205
1206 /* To perform case-insensitive searches through model convert all search terms
1207 * to lower-case before starting search.
1208 * Remember that string list must be NULL terminated.
1209 */
1210 numberTerms=g_strv_length((gchar**)inSearchTerms);
1211 if(numberTerms==0)
1212 {
1213 /* If we get here no search term is given, return no result set */
1214 return(NULL);
1215 }
1216
1217 terms=g_new(gchar*, numberTerms+1);
1218 if(!terms)
1219 {
1220 g_critical("Could not allocate memory to copy search criteria for case-insensitive search");
1221 return(NULL);
1222 }
1223
1224 termsIter=terms;
1225 while(*inSearchTerms)
1226 {
1227 *termsIter=g_utf8_strdown(*inSearchTerms, -1);
1228
1229 /* Move to next entry where to store lower-case and
1230 * initialize with NULL for NULL termination of list.
1231 */
1232 termsIter++;
1233 *termsIter=NULL;
1234
1235 /* Move to next search term to convert to lower-case */
1236 inSearchTerms++;
1237 }
1238
1239 /* Create empty result set to store matching result items */
1240 resultSet=xfdashboard_search_result_set_new();
1241
1242 /* Perform search */
1243 for(iter=priv->allApps; iter; iter=g_list_next(iter))
1244 {
1245 /* Get app info to check for match */
1246 appInfo=XFDASHBOARD_DESKTOP_APP_INFO(iter->data);
1247
1248 /* If desktop app info should be hidden then continue with next one */
1249 if(!g_app_info_should_show(G_APP_INFO(appInfo)))
1250 {
1251 continue;
1252 }
1253
1254 /* Check for a match against search terms */
1255 score=_xfdashboard_applications_search_provider_score(self, terms, G_APP_INFO(appInfo));
1256 if(score>=0.0f)
1257 {
1258 GVariant *resultItem;
1259
1260 /* Create result item */
1261 resultItem=g_variant_new_string(g_app_info_get_id(G_APP_INFO(appInfo)));
1262
1263 /* Add result item to result set */
1264 xfdashboard_search_result_set_add_item(resultSet, resultItem);
1265 xfdashboard_search_result_set_set_item_score(resultSet, resultItem, score);
1266 }
1267 }
1268
1269 /* Sort result set */
1270 xfdashboard_search_result_set_set_sort_func_full(resultSet,
1271 _xfdashboard_applications_search_provider_sort_result_set,
1272 g_object_ref(self),
1273 g_object_unref);
1274
1275 /* Release allocated resources */
1276 if(terms)
1277 {
1278 termsIter=terms;
1279 while(*termsIter)
1280 {
1281 g_free(*termsIter);
1282 termsIter++;
1283 }
1284 g_free(terms);
1285 }
1286
1287 /* Return result set */
1288 return(resultSet);
1289 }
1290
1291 /* Create actor for a result item of the result set returned from a search request */
_xfdashboard_applications_search_provider_create_result_actor(XfdashboardSearchProvider * inProvider,GVariant * inResultItem)1292 static ClutterActor* _xfdashboard_applications_search_provider_create_result_actor(XfdashboardSearchProvider *inProvider,
1293 GVariant *inResultItem)
1294 {
1295 XfdashboardApplicationsSearchProvider *self;
1296 XfdashboardApplicationsSearchProviderPrivate *priv;
1297 ClutterActor *actor;
1298 ClutterAction *clickAction;
1299 ClutterAction *dragAction;
1300 GAppInfo *appInfo;
1301
1302 g_return_val_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(inProvider), NULL);
1303 g_return_val_if_fail(inResultItem, NULL);
1304
1305 self=XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER(inProvider);
1306 priv=self->priv;
1307
1308 /* Get app info for result item */
1309 appInfo=xfdashboard_application_database_lookup_desktop_id(priv->appDB, g_variant_get_string(inResultItem, NULL));
1310 if(!appInfo) appInfo=xfdashboard_desktop_app_info_new_from_desktop_id(g_variant_get_string(inResultItem, NULL));
1311 if(!appInfo)
1312 {
1313 g_warning("Cannot create actor for desktop ID '%s' in result set of %s",
1314 g_variant_get_string(inResultItem, NULL),
1315 G_OBJECT_TYPE_NAME(inProvider));
1316 return(NULL);
1317 }
1318
1319 /* Create actor for result item */
1320 actor=xfdashboard_application_button_new_from_app_info(appInfo);
1321 clutter_actor_show(actor);
1322
1323 clickAction=xfdashboard_click_action_new();
1324 g_signal_connect_swapped(clickAction, "clicked", G_CALLBACK(_xfdashboard_applications_search_provider_on_popup_menu), self);
1325 clutter_actor_add_action(actor, clickAction);
1326
1327 dragAction=xfdashboard_drag_action_new();
1328 clutter_drag_action_set_drag_threshold(CLUTTER_DRAG_ACTION(dragAction), -1, -1);
1329 clutter_actor_add_action(actor, dragAction);
1330 g_signal_connect(dragAction, "drag-begin", G_CALLBACK(_xfdashboard_applications_search_provider_on_drag_begin), self);
1331 g_signal_connect(dragAction, "drag-end", G_CALLBACK(_xfdashboard_applications_search_provider_on_drag_end), self);
1332
1333 /* Release allocated resources */
1334 g_object_unref(appInfo);
1335
1336 /* Return created actor */
1337 return(actor);
1338 }
1339
1340 /* Activate result item */
_xfdashboard_applications_search_provider_activate_result(XfdashboardSearchProvider * inProvider,GVariant * inResultItem,ClutterActor * inActor,const gchar ** inSearchTerms)1341 static gboolean _xfdashboard_applications_search_provider_activate_result(XfdashboardSearchProvider* inProvider,
1342 GVariant *inResultItem,
1343 ClutterActor *inActor,
1344 const gchar **inSearchTerms)
1345 {
1346 XfdashboardApplicationButton *button;
1347
1348 g_return_val_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(inProvider), FALSE);
1349 g_return_val_if_fail(XFDASHBOARD_IS_APPLICATION_BUTTON(inActor), FALSE);
1350
1351 button=XFDASHBOARD_APPLICATION_BUTTON(inActor);
1352
1353 /* Launch application */
1354 return(xfdashboard_application_button_execute(button, NULL));
1355 }
1356
1357 /* IMPLEMENTATION: GObject */
1358
1359 /* Dispose this object */
_xfdashboard_applications_search_provider_dispose(GObject * inObject)1360 static void _xfdashboard_applications_search_provider_dispose(GObject *inObject)
1361 {
1362 XfdashboardApplicationsSearchProvider *self=XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER(inObject);
1363 XfdashboardApplicationsSearchProviderPrivate *priv=self->priv;
1364
1365 /* Release allocated resouces */
1366 if(priv->appDB)
1367 {
1368 if(priv->applicationAddedID)
1369 {
1370 g_signal_handler_disconnect(priv->appDB, priv->applicationAddedID);
1371 priv->applicationAddedID=0;
1372 }
1373
1374 if(priv->applicationRemovedID)
1375 {
1376 g_signal_handler_disconnect(priv->appDB, priv->applicationRemovedID);
1377 priv->applicationRemovedID=0;
1378 }
1379
1380 g_object_unref(priv->appDB);
1381 priv->appDB=NULL;
1382 }
1383
1384 if(priv->allApps)
1385 {
1386 g_list_free_full(priv->allApps, g_object_unref);
1387 priv->allApps=NULL;
1388 }
1389
1390 if(priv->xfconfSortModeBindingID)
1391 {
1392 xfconf_g_property_unbind(priv->xfconfSortModeBindingID);
1393 priv->xfconfSortModeBindingID=0;
1394 }
1395
1396 if(priv->xfconfChannel)
1397 {
1398 priv->xfconfChannel=NULL;
1399 }
1400
1401 /* Call parent's class dispose method */
1402 G_OBJECT_CLASS(xfdashboard_applications_search_provider_parent_class)->dispose(inObject);
1403 }
1404
1405 /* Set/get properties */
_xfdashboard_applications_search_provider_set_property(GObject * inObject,guint inPropID,const GValue * inValue,GParamSpec * inSpec)1406 static void _xfdashboard_applications_search_provider_set_property(GObject *inObject,
1407 guint inPropID,
1408 const GValue *inValue,
1409 GParamSpec *inSpec)
1410 {
1411 XfdashboardApplicationsSearchProvider *self=XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER(inObject);
1412
1413 switch(inPropID)
1414 {
1415 case PROP_SORT_MODE:
1416 xfdashboard_applications_search_provider_set_sort_mode(self, g_value_get_flags(inValue));
1417 break;
1418
1419 default:
1420 G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
1421 break;
1422 }
1423 }
1424
_xfdashboard_applications_search_provider_get_property(GObject * inObject,guint inPropID,GValue * outValue,GParamSpec * inSpec)1425 static void _xfdashboard_applications_search_provider_get_property(GObject *inObject,
1426 guint inPropID,
1427 GValue *outValue,
1428 GParamSpec *inSpec)
1429 {
1430 XfdashboardApplicationsSearchProvider *self=XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER(inObject);
1431 XfdashboardApplicationsSearchProviderPrivate *priv=self->priv;
1432
1433 switch(inPropID)
1434 {
1435 case PROP_SORT_MODE:
1436 g_value_set_flags(outValue, priv->nextSortMode);
1437 break;
1438
1439 default:
1440 G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
1441 break;
1442 }
1443 }
1444
1445 /* Class initialization
1446 * Override functions in parent classes and define properties
1447 * and signals
1448 */
xfdashboard_applications_search_provider_class_init(XfdashboardApplicationsSearchProviderClass * klass)1449 static void xfdashboard_applications_search_provider_class_init(XfdashboardApplicationsSearchProviderClass *klass)
1450 {
1451 XfdashboardSearchProviderClass *providerClass=XFDASHBOARD_SEARCH_PROVIDER_CLASS(klass);
1452 GObjectClass *gobjectClass=G_OBJECT_CLASS(klass);
1453
1454 /* Override functions */
1455 gobjectClass->dispose=_xfdashboard_applications_search_provider_dispose;
1456 gobjectClass->set_property=_xfdashboard_applications_search_provider_set_property;
1457 gobjectClass->get_property=_xfdashboard_applications_search_provider_get_property;
1458
1459 providerClass->initialize=_xfdashboard_applications_search_provider_initialize;
1460 providerClass->get_name=_xfdashboard_applications_search_provider_get_name;
1461 providerClass->get_icon=_xfdashboard_applications_search_provider_get_icon;
1462 providerClass->get_result_set=_xfdashboard_applications_search_provider_get_result_set;
1463 providerClass->create_result_actor=_xfdashboard_applications_search_provider_create_result_actor;
1464 providerClass->activate_result=_xfdashboard_applications_search_provider_activate_result;
1465
1466 /* Define properties */
1467 XfdashboardApplicationsSearchProviderProperties[PROP_SORT_MODE]=
1468 g_param_spec_flags("sort-mode",
1469 "Sort mode",
1470 "Defines how to sort matching applications",
1471 XFDASHBOARD_TYPE_APPLICATIONS_SEARCH_PROVIDER_SORT_MODE,
1472 XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_SORT_MODE_NONE,
1473 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
1474
1475 g_object_class_install_properties(gobjectClass, PROP_LAST, XfdashboardApplicationsSearchProviderProperties);
1476 }
1477
1478 /* Object initialization
1479 * Create private structure and set up default values
1480 */
xfdashboard_applications_search_provider_init(XfdashboardApplicationsSearchProvider * self)1481 static void xfdashboard_applications_search_provider_init(XfdashboardApplicationsSearchProvider *self)
1482 {
1483 XfdashboardApplicationsSearchProviderPrivate *priv;
1484
1485 self->priv=priv=xfdashboard_applications_search_provider_get_instance_private(self);
1486
1487 /* Set up default values */
1488 priv->xfconfChannel=xfdashboard_application_get_xfconf_channel(NULL);
1489 priv->currentSortMode=XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_SORT_MODE_NONE;
1490 priv->nextSortMode=XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_SORT_MODE_NONE;
1491
1492 /* Get application database */
1493 priv->appDB=xfdashboard_application_database_get_default();
1494 priv->applicationAddedID=g_signal_connect_swapped(priv->appDB,
1495 "application-added",
1496 G_CALLBACK(_xfdashboard_applications_search_provider_on_application_added),
1497 self);
1498 priv->applicationRemovedID=g_signal_connect_swapped(priv->appDB,
1499 "application-removed",
1500 G_CALLBACK(_xfdashboard_applications_search_provider_on_application_removed),
1501 self);
1502
1503 /* Get list of all installed applications */
1504 priv->allApps=xfdashboard_application_database_get_all_applications(priv->appDB);
1505
1506 /* Bind to xfconf to react on changes */
1507 priv->xfconfSortModeBindingID=
1508 xfconf_g_property_bind(priv->xfconfChannel,
1509 SORT_MODE_XFCONF_PROP,
1510 G_TYPE_UINT,
1511 self,
1512 "sort-mode");
1513 }
1514
1515 /* IMPLEMENTATION: Public API */
1516
1517 /* Get/set sorting mode */
xfdashboard_applications_search_provider_get_sort_mode(XfdashboardApplicationsSearchProvider * self)1518 XfdashboardApplicationsSearchProviderSortMode xfdashboard_applications_search_provider_get_sort_mode(XfdashboardApplicationsSearchProvider *self)
1519 {
1520 g_return_val_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(self), XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_SORT_MODE_NONE);
1521
1522 return(self->priv->nextSortMode);
1523 }
1524
xfdashboard_applications_search_provider_set_sort_mode(XfdashboardApplicationsSearchProvider * self,const XfdashboardApplicationsSearchProviderSortMode inMode)1525 void xfdashboard_applications_search_provider_set_sort_mode(XfdashboardApplicationsSearchProvider *self, const XfdashboardApplicationsSearchProviderSortMode inMode)
1526 {
1527 XfdashboardApplicationsSearchProviderPrivate *priv;
1528
1529 g_return_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(self));
1530
1531 priv=self->priv;
1532
1533 /* Set value if changed */
1534 if(priv->nextSortMode!=inMode)
1535 {
1536 /* Set value */
1537 priv->nextSortMode=inMode;
1538
1539 /* Notify about property change */
1540 g_object_notify_by_pspec(G_OBJECT(self), XfdashboardApplicationsSearchProviderProperties[PROP_SORT_MODE]);
1541 }
1542 }
1543